spring security验证流程:

  • spring security的登录验证是由UsernamePasswordAuthenticationFilter这个过滤器完成,在该类的父类AbstractAuthenticationProcessingFilter(585行)中有一个AuthenticationManager接口属性,验证工作主要是通过这个AuthenticationManager接口的实例来完成的。默认情况下,spring security会把ProviderManager类的实例注入到该属性。而自定义AbstractAuthenticationProcessingFilter的情况下,一般会在配置文件:,起别名,然后注入该自定义类的某个属性中,看上面那个配置文件就知道了
  • UsernamePasswordAuthenticationFilter的验证过程如下:
    • 首先过滤器会调用自身的attemptAuthentication方法(646行),该方法规定请求必须为post,并从request中取出authentication,authentication是在SecurityContextPersistenceFilter过滤器中通过捕获用户提交的登录表单中的内容生成的一个Authentication接口实例。
    • attemptAuthentication方法拿到authentication对象后,在方法中又调用ProviderManager类的authenticate方法并传入authentication对象(由于只能验证用户名、密码的简单信息,所以经常重写以实现复杂的用户验证,并返回包含当前用户所有信息的authentication),验证用户是否能登录,并处理定义登录成败重定向的页面
    • 也就是说attemptAuthentication方法会调用类中的List providers集合中各个AuthenticationProvider接口实现类中的authenticate(Authentication authentication)进行验证
    • provider的实现类在验证用户时,会调用UserDetailsService的实现类的loadUserByUsername方法获取用户信息。
  • 由此可见,真正的验证逻辑是由各个AuthenticationProvider接口实现类来完成的。DaoAuthenticationProvider类是默认情况下会被注入AuthenticationProvider的接口实现类。

源码分析

  • AbstractAuthenticationProcessingFilter

    public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
        implements ApplicationEventPublisherAware, MessageSourceAware {
    
        // 过滤器doFilter方法
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
    
            /*
             * 1、判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理。
             */
            if (!requiresAuthentication(request, response)) {
                chain.doFilter(request, response);
    
                return;
            }
    
            if (logger.isDebugEnabled()) {
                logger.debug("Request is to process authentication");
            }
    
            Authentication authResult;
    
            /*
             * 2、调用子类(UsernamePasswordAuthenticationFilter)attemptAuthentication方法
             */
            try {
                authResult = attemptAuthentication(request, response);
                if (authResult == null) {
                    // return immediately as subclass has indicated that it hasn't completed
                    // authentication
                    return;
                }
    
                /*
                 * 3、最终认证成功后,会处理一些与session相关的方法
                 * (比如将认证信息存到session等操作)。
                 */ 
                sessionStrategy.onAuthentication(authResult, request, response);
            }
    
            /*
             * 3、认证失败后的一些处理。
             */ 
            catch (InternalAuthenticationServiceException failed) {
                logger.error(
                        "An internal error occurred while trying to authenticate the user.",
                        failed);
                unsuccessfulAuthentication(request, response, failed);
                return;
            }
            catch (AuthenticationException failed) {
                unsuccessfulAuthentication(request, response, failed);
                return;
            }
            /*
             * 4、认证成功,继续下个请求
             */
            if (continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
    
            /*
             * 5、最终认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中
             * 并调用认证成功处理器AuthenticationSuccessHandler接口实现类的
             * onAuthenticationSuccess()。
             */
            successfulAuthentication(request, response, chain, authResult);
        }
    }
    
  • UsernamePasswordAuthenticaionFilter,以上AbstractAuthenticationProcessingFilter的子类,主要是重写attemptAuthentication(),规定请求必须为post。

    public class UsernamePasswordAuthenticationFilter 
            extends AbstractAuthenticationProcessingFilter {
    
        //实现父类的方法
        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();
            /*
             * 封装进Authentication实现类UsernamePasswordAuthenticationToken
             * public UsernamePasswordAuthenticationToken(Object principal, Object credentials)    {
                    super((Collection)null); //设置权限为null
                    this.principal = principal;
                    this.credentials = credentials;
                    this.setAuthenticated(false);
                }
             */
            UsernamePasswordAuthenticationToken authRequest = 
                new UsernamePasswordAuthenticationToken(
                    username, password); //一般来说principle是User对象较常见
            setDetails(request, authRequest); //Allow subclasses to set the "details" property
    
            //调用AuthenticationManager管理下的AuthenticationProvider接口实现类的authenticate()
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
    
  • AuthenticationManager:AuthenticationManager仅仅是一个接口,默认实现类是ProviderManager,ProviderManager不是自己直接对请求进行验证,而是将其委派给一个AuthenticationProvider列表。例如我们要同时使用jdbc认证和ldap认证,那么就可以用这个列表。列表中的每一个AuthenticationProvider将会被一次查询是否需要通过器进行验证。每个provider的验证结果只有两种情况:抛出ProviderNotFoundException异常或者完全填充一个Authentication对象,并存储在SecurityContext中。
  • ProviderManager

    public Authentication authenticate(A