`

UsernamePasswordAuthenticationFilter 源码分析

 
阅读更多

 

AbstractAuthenticationProcessingFilter

 

1、doFilter->attemptAuthentication

UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter

 

2、attemptAuthentication-> authenticate

ProviderManager implements AuthenticationManager

 

3.authenticate->authenticate

AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider

 

4。authenticate->retrieveUser

DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider

 

 5。retrieveUser->UserDetailsServiceImp

 UserDetailsServiceImp implements UserDetailsServiceder

    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);
    }

 这里的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继承于AuthenticationManager。实际上authenticate方法由ProviderManager的定义,代码如下
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();

        for (AuthenticationProvider provider : getProviders()) {
            if (!provider.supports(toTest)) {
                continue;
            }

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

            try {
                result = provider.authenticate(authentication);

                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            } catch (AccountStatusException e) {
                prepareException(e, authentication);
                // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status
                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) {
            if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
                // Authentication is complete. Remove credentials and other secret data from authentication
                ((CredentialsContainer)result).eraseCredentials();
            }

            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}"));
        }

        prepareException(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方法 

    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);
    }

 

 

 

    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实现用户登录.docx

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

    项目集成Spring Security.docx

    UsernamePasswordAuthenticationFilter 表示表单登陆过滤器 BasicAuthenticationFilter 表示 httpbaic 方式登陆过滤器 图中橙色的 FilterSecurityInterceptor 是最终的过滤器,它会决定当前的请求可不可以访问...

    yueting-api:悦庭Restfull API后端

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

    security-parent

    5 = {UsernamePasswordAuthenticationFilter @ 7755} 6 = {RequestCacheAwareFilter @ 9937} 7 = {SecurityContextHolderAwareRequestFilter @ 9938} 8 = {AnonymousAuthenticationFilter @ 9939} 9 = {...

    springboot-springsecurity-jwt-demo

    该类继承自UsernamePasswordAuthenticationFilter,重写了其中的2个方法: attemptAuthentication :接收并解析用户凭证。 successfulAuthentication :用户成功登录后,这个方法会被调用,我们在这个方法里生成...

    Spring Security 中文教程.pdf

    8.4. UsernamePasswordAuthenticationFilter 8.4.1. 认证成功和失败的应用流程 9. Basic(基本)和Digest(摘要)验证 9.1. BasicAuthenticationFilter 9.1.1. 配置 9.2. DigestAuthenticationFilter ...

    SpringSecurity 3.0.1.RELEASE.CHM

    8.4. UsernamePasswordAuthenticationFilter 8.4.1. 认证成功和失败的应用流程 9. Basic(基本)和Digest(摘要)验证 9.1. BasicAuthenticationFilter 9.1.1. 配置 9.2. DigestAuthenticationFilter 9.2.1. ...

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

    8.4. UsernamePasswordAuthenticationFilter 8.4.1. 认证成功和失败的应用流程 9. Basic(基本)和Digest(摘要)验证 9.1. BasicAuthenticationFilter 9.1.1. 配置 9.2. DigestAuthenticationFilter ...

    spring security 参考手册中文版

    14.4 UsernamePasswordAuthenticationFilter 125 14.4.1认证成功与失败的应用流程 125 15. Servlet API集成 127 15.1 Servlet 2.5+集成 127 15.1.1 HttpServletRequest.getRemoteUser() 127 15.1.2 ...

Global site tag (gtag.js) - Google Analytics