`
Dead_knight
  • 浏览: 1193984 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
博客专栏
752c8642-b795-3fe6-946e-a4e845bffdec
Spring Securi...
浏览量:238272
33caa84e-18a6-3036-a82b-6e2106a4de63
clojure专题
浏览量:48085
E17ca077-44df-3816-a3fe-471c43f6e1e5
WebLogic11g
浏览量:235937
社区版块
存档分类
最新评论

Spring Security3源码分析-UsernamePasswordAuthenticationFilter分析

阅读更多
UsernamePasswordAuthenticationFilter过滤器对应的类路径为
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
实际上这个Filter类的doFilter是父类AbstractAuthenticationProcessingFilter的
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        //判断form-login标签是否包含login-processing-url属性
          //如果没有采用默认的url:j_spring_security_check
        //如果拦截的url不需要认证,直接跳过
        if (!requiresAuthentication(request, response)) {
            chain.doFilter(request, response);

            return;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Request is to process authentication");
        }

        Authentication authResult;

        try {
            //由子类完成认证
            authResult = attemptAuthentication(request, response);
            if (authResult == null) {
                // return immediately as subclass has indicated that it hasn't completed authentication
                return;
            }
            //session策略处理认证信息
              //sessionStrategy是通过session-management标签中定义的
              //session管理策略构造的SessionAuthenticationStrategy
            //具体的session管理比较复杂,部分后面单个篇幅讲解
            sessionStrategy.onAuthentication(authResult, request, response);
        }
        catch (AuthenticationException failed) {
            // Authentication failed
            //认证失败处理
            unsuccessfulAuthentication(request, response, failed);

            return;
        }

        // Authentication success
        if (continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        //认证成功处理
         //1.向SecurityContext中设置Authentication认证信息
         //2.如果有remember me服务,则查找请求参数中是否包含_spring_security_remember_me,如果该参数值为true、yes、on、1则执行remember me功能:添加cookie、入库。为下次请求时自动登录做准备
         //3.发布认证成功事件
         //4.执行跳转
        successfulAuthentication(request, response, authResult);
    }

子类UsernamePasswordAuthenticationFilter的认证方法attemptAuthentication
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //只处理post提交的请求
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        //获取用户名、密码数据
        String username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();
        //构造未认证的UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

        // Place the last username attempted into HttpSession for views
        HttpSession session = request.getSession(false);
        //如果session不为空,添加username到session中
        if (session != null || getAllowSessionCreation()) {
            request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(username));
        }

        // Allow subclasses to set the "details" property
        //设置details,这里就是设置org.springframework.security.web.
        //authentication.WebAuthenticationDetails实例到details中
        setDetails(request, authRequest);
        //通过AuthenticationManager:ProviderManager完成认证任务
        return this.getAuthenticationManager().authenticate(authRequest);
    }

这里的authenticationManager变量也是通过解析form-login标签,构造bean时注入的,具体解析类为:org.springframework.security.config.http.AuthenticationConfigBuilder
代码片段为:
    void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager) {

        Element formLoginElt = DomUtils.getChildElementByTagName(httpElt, Elements.FORM_LOGIN);

        if (formLoginElt != null || autoConfig) {
            FormLoginBeanDefinitionParser parser = new FormLoginBeanDefinitionParser("/j_spring_security_check",
                    AUTHENTICATION_PROCESSING_FILTER_CLASS, requestCache, sessionStrategy);

            parser.parse(formLoginElt, pc);
            formFilter = parser.getFilterBean();
            formEntryPoint = parser.getEntryPointBean();
        }

        if (formFilter != null) {
            formFilter.getPropertyValues().addPropertyValue("allowSessionCreation", new Boolean(allowSessionCreation));
            //设置authenticationManager的bean依赖
            formFilter.getPropertyValues().addPropertyValue("authenticationManager", authManager);


            // Id is required by login page filter
            formFilterId = pc.getReaderContext().generateBeanName(formFilter);
            pc.registerBeanComponent(new BeanComponentDefinition(formFilter, formFilterId));
            injectRememberMeServicesRef(formFilter, rememberMeServicesId);
        }
    }


继续看ProviderManager代码。实际上authenticate方法由ProviderManager的父类定义,并且authenticate方法内调用子类的doAuthentication方法,记得这是设计模式中的模板模式
    public Authentication doAuthentication(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        //循环ProviderManager中的providers,由具体的provider执行认证操作
        for (AuthenticationProvider provider : getProviders()) {
        	System.out.println("AuthenticationProvider: " + provider.getClass().getName());
            if (!provider.supports(toTest)) {
                continue;
            }

            logger.debug("Authentication attempt using " + provider.getClass().getName());

            try {
                result = provider.authenticate(authentication);

                if (result != null) {
                    //复制details
                    copyDetails(authentication, result);
                    break;
                }
            } catch (AccountStatusException e) {
                // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status
                eventPublisher.publishAuthenticationFailure(e, authentication);
                throw e;
            } catch (AuthenticationException e) {
                lastException = e;
            }
        }

        if (result == null && parent != null) {
            // Allow the parent to try.
            try {
                result = parent.authenticate(authentication);
            } catch (ProviderNotFoundException e) {
                // ignore as we will throw below if no other exception occurred prior to calling parent and the parent
                // may throw ProviderNotFound even though a provider in the child already handled the request
            } catch (AuthenticationException e) {
                lastException = e;
            }
        }

        if (result != null) {
            eventPublisher.publishAuthenticationSuccess(result);
            return result;
        }

        // Parent was null, or didn't authenticate (or throw an exception).

        if (lastException == null) {
            lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",
                        new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}"));
        }
        //由注入进来的org.springframework.security.authentication.DefaultAuthenticationEventPublisher完成事件发布任务
        eventPublisher.publishAuthenticationFailure(lastException, authentication);

        throw lastException;
    }


ProviderManager类中的providers由哪些provider呢?如果看完authentication-manager标签解析的讲解,应该知道注入到providers中的provider分别为:
org.springframework.security.authentication.dao.DaoAuthenticationProvider
org.springframework.security.authentication.AnonymousAuthenticationProvider
其他的provider根据特殊情况,再添加到providers中的,如remember me功能的provider
org.springframework.security.authentication.RememberMeAuthenticationProvider

可以看出来,ProviderManager仅仅是管理provider的,具体的authenticate认证任务由各自provider来完成。

现在来看DaoAuthenticationProvider的认证处理,实际上authenticate由父类AbstractUserDetailsAuthenticationProvider完成。代码如下
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        …………
         //获取登录的用户名
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

        boolean cacheWasUsed = true;
        //如果配置了缓存,从缓存中获取UserDetails实例
        UserDetails user = this.userCache.getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;

            try {
                //如果UserDetails为空,则由具体子类DaoAuthenticationProvider
                //根据用户名、authentication获取UserDetails
                user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
            } catch (UsernameNotFoundException notFound) {
                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                } else {
                    throw notFound;
                }
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            //一些认证检查(账号是否可用、是否过期、是否被锁定)
            preAuthenticationChecks.check(user);
            //额外的密码检查(salt、passwordEncoder)
            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
        } catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                // There was a problem, so try again after checking
                // we're using latest data (i.e. not from the cache)
                cacheWasUsed = false;
                user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
            } else {
                throw exception;
            }
        }
        //检查账号是否过期
        postAuthenticationChecks.check(user);
        //添加UserDetails到缓存中
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
        //返回成功认证后的Authentication
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }


继续看DaoAuthenticationProvider的retrieveUser方法
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        UserDetails loadedUser;

        try {
            //最关键的部分登场了
              //UserDetailService就是authentication-provider标签中定义的
              //属性user-service-ref
            loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        }
        catch (DataAccessException repositoryProblem) {
            throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
        }

        if (loadedUser == null) {
            throw new AuthenticationServiceException(
                    "UserDetailsService returned null, which is an interface contract violation");
        }
        return loadedUser;
    }

实际上,只要实现UserDetailsService接口的loadUserByUsername方法,就完成了登录认证的工作
<authentication-manager alias="authenticationManager">
    <authentication-provider user-service-ref="userDetailsManager"/>
</authentication-manager>

很多教程上说配置JdbcUserDetailsManager这个UserDetailsService,实际上该类的父类
JdbcDaoImpl方法loadUserByUsername代码如下:
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
        //根据username从数据库中查询User数据
        List<UserDetails> users = loadUsersByUsername(username);

        if (users.size() == 0) {
            throw new UsernameNotFoundException(
                    messages.getMessage("JdbcDaoImpl.notFound", new Object[]{username}, "Username {0} not found"), username);
        }

        UserDetails user = users.get(0); // contains no GrantedAuthority[]

        Set<GrantedAuthority> dbAuthsSet = new HashSet<GrantedAuthority>();
        //添加授权信息
        if (enableAuthorities) {
            dbAuthsSet.addAll(loadUserAuthorities(user.getUsername()));
        }
        //是否使用了Group
        if (enableGroups) {
            dbAuthsSet.addAll(loadGroupAuthorities(user.getUsername()));
        }

        List<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>(dbAuthsSet);

        addCustomAuthorities(user.getUsername(), dbAuths);

        if (dbAuths.size() == 0) {
            throw new UsernameNotFoundException(
                    messages.getMessage("JdbcDaoImpl.noAuthority",
                            new Object[] {username}, "User {0} has no GrantedAuthority"), username);
        }

        return createUserDetails(username, user, dbAuths);
    }

    //usersByUsernameQuery查询语句可配置
     //直接从数据库中查询该username对应的数据,并构造User对象
    protected List<UserDetails> loadUsersByUsername(String username) {
        return getJdbcTemplate().query(usersByUsernameQuery, new String[] {username}, new RowMapper<UserDetails>() {
            public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
                String username = rs.getString(1);
                String password = rs.getString(2);
                boolean enabled = rs.getBoolean(3);
                return new User(username, password, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES);
            }

        });
    }

……
    protected UserDetails createUserDetails(String username, UserDetails userFromUserQuery,
            List<GrantedAuthority> combinedAuthorities) {
        String returnUsername = userFromUserQuery.getUsername();

        if (!usernameBasedPrimaryKey) {
            returnUsername = username;
        }
        //根据用户名、密码、enabled、授权列表构造UserDetails实例User
        return new User(returnUsername, userFromUserQuery.getPassword(), userFromUserQuery.isEnabled(),
                true, true, true, combinedAuthorities);
    }

其他的provider,如
RememberMeAuthenticationProvider、AnonymousAuthenticationProvider的认证处理都很简单,首先判断是否支持Authentication,不支持直接返回null,支持也不处理直接返回该Authentication

这里需要强调一下,DaoAuthenticationProvider只支持UsernamePasswordAuthenticationToken这个Authentication。如果对其他的Authentication,DaoAuthenticationProvider是不做处理的
分享到:
评论

相关推荐

    Spring Security-3.0.1中文官方文档(翻译版)

    Spring Security-3.0.1 中文官方文档(翻译版) 这次发布的Spring Security-3.0.1 是一个bug fix 版,主要是对3.0 中存在的一些问题进 行修 正。文档中没有添加新功能的介绍,但是将之前拼写错误的一些类名进行...

    springboot-springsecurity-jwt-demo

    [输入图片说明](https://gitee.com/uploads/images/2018/0211/154308_9576ce90_130820.png "jwt-3.png") 4.用登录成功后拿到的token再次请求/users/userList接口 4.1将请求中的XXXXXX替换成拿到的token ...

    Spring Security 中文教程.pdf

    1.1. Spring Security是什么? 1.2. 历史 1.3. 发行版本号 1.4. 获得Spring Security 1.4.1. 项目模块 1.4.1.1. Core - spring-security-core.jar 1.4.1.2. Web - spring-security-web.jar 1.4.1.3. ...

    spring security 参考手册中文版

    3. Spring Security 4.2的新特性 27 3.1 Web改进 27 3.2配置改进 28 3.3杂项 28 4.样品和指南(从这里开始) 28 5. Java配置 29 5.1 Hello Web安全Java配置 29 5.1.1 AbstractSecurityWebApplicationInitializer 31 ...

    SpringSecurity 3.0.1.RELEASE.CHM

    1.1. Spring Security是什么? 1.2. 历史 1.3. 发行版本号 1.4. 获得Spring Security 1.4.1. 项目模块 1.4.1.1. Core - spring-security-core.jar 1.4.1.2. Web - spring-security-web.jar 1.4.1.3. Config -...

    Spring Security实现用户登录.docx

    在集成 Spring Security 安全框架的时候我们最先处理的可能就是根据我们项目的实际需要来定制注册登录了,尤其是 Http 登录认证。根据以前的相关文章介绍, Http 登录...今天我们就简单分析它的源码和工作流程。

    项目集成Spring Security.docx

    Security 有两种认证方式: httpbasic formLogin 默认的,如上边那种方式 同样,Security 也提供两种过滤器类: UsernamePasswordAuthenticationFilter 表示表单登陆过滤器 BasicAuthenticationFilter 表示 ...

    security-parent

    3 = {CsrfFilter @ 7758} 4 = {LogoutFilter @ 7757} 5 = {UsernamePasswordAuthenticationFilter @ 7755} 6 = {RequestCacheAwareFilter @ 9937} 7 = {SecurityContextHolderAwareRequestFilter @ 9938} 8 = {...

    yueting-api:悦庭Restfull API后端

    传统的方法是在认证通过后,创建...核心功能是在验证用户名密码正确后,生成一个token,并将token返回给客户端:该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法:attemptAuthentication :接

Global site tag (gtag.js) - Google Analytics