美文网首页spring
AuthenticationManager 验证原理分析

AuthenticationManager 验证原理分析

作者: 凌云v | 来源:发表于2017-05-11 13:32 被阅读0次

    UsernamePasswordAuthenticationFilter 源码分析 中,最后在类UsernamePasswordAuthenticationFilter 的验证方法 attemptAuthentication() 会将用户表单提交过来的用户名和密码封装成对象委托类 AuthenticationManager 的验证方法 authenticate() 进行身份验证。

    那么本文主要对 AuthenticationManager 的验证方法 authenticate() 验证原理进行源码分析。

    AuthenticationManager 相关类图

    AuthenticationManager 验证过程涉及到的类和接口较多,先用一张类图说明各个类和接口之间的关系,如下:

    AuthenticationManager相关类图.png
    • AuthenticationManager 为认证管理接口类,其定义了认证方法 authenticate()
    • ProviderManager 为认证管理类,实现了接口 AuthenticationManager ,并在认证方法 authenticate() 中将身份认证委托给具有认证资格的 AuthenticationProvider 进行身份认证。
      ProviderManager中的成员变量 providers [List<AuthenticationProvider>] 存储了一个 AuthenticationProvider 类型的 List,和 spring security 配置文件相对应,如下图:
    AuthenticationManager 对比图
    • AuthenticationProvider 为认证接口类,其定义了身份认证方法 authenticate()
    • AbstractUserDetailsAuthenticationProvider 为认证抽象类,实现了接口 AuthenticationProvider 定义的认证方法 authenticate()
      AbstractUserDetailsAuthenticationProvider 还定义了虚拟方法 retrieveUser() 用作查询数据库用户信息,以及虚拟方法 additionalAuthenticationChecks() 用作身份认证。
    • DaoAuthenticationProvider 继承自类 AbstractUserDetailsAuthenticationProvider,实现该类的方法 retrieveUser()additionalAuthenticationChecks()
      DaoAuthenticationProvider 中还具有成员变量 userDetailsService [UserDetailsService] 用作用户信息查询,以及成员变量 passwordEncoder [PasswordEncoder] 用作密码的加密及验证。
      DaoAuthenticationProviderspring security 配置文件相对应,如下图所示:
    authenticationProvider 对比图

    流程分析

    1、认证的入口为 AuthenticationManagerauthenticate()方法,鉴于 AuthenticationManager是接口类,因此分析它的实现类 ProviderManagerProviderManagerauthenticate() 方法代码如下:

        public Authentication authenticate(Authentication authentication)
                throws AuthenticationException {
            Class<? extends Authentication> toTest = authentication.getClass();
            AuthenticationException lastException = null;
            Authentication result = null;
            boolean debug = logger.isDebugEnabled();
    
    -->1    for (AuthenticationProvider provider : getProviders()) {
                if (!provider.supports(toTest)) {
                    continue;
                }
    
                if (debug) {
                    logger.debug("Authentication attempt using "
                            + provider.getClass().getName());
                }
    
                try {
    -->2            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 (InternalAuthenticationServiceException e) {
                    prepareException(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) {
                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;
        }
    

    其中:

    • -->1 处的 for 循环从该类的属性 providers[List<AuthenticationProvider>] 中去取到支持该认证的 AuthenticationProvider 来进行认证处理。
    • -->2 处代码为使用支持该认证的 AuthenticationProvider 对用户身份进行认证,使用该类进行认证如下文所示。

    2、在上文代码的 -->2 处调用的代码 result = provider.authenticate(authentication);,使用了 AuthenticationProviderauthenticate() 方法进行认证,接下来分析该方法,鉴于 AuthenticationProvider 是一个接口,因此分析它的实现类 AbstractUserDetailsAuthenticationProvider 的子类 DaoAuthenticationProvider 的认证方法 authenticate(),代码如下所示:

    public Authentication authenticate(Authentication authentication)
                throws AuthenticationException {
            Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                    messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.onlySupports",
                            "Only UsernamePasswordAuthenticationToken is supported"));
    
            // Determine username
            String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                    : authentication.getName();
    
            boolean cacheWasUsed = true;
            UserDetails user = this.userCache.getUserFromCache(username);
    
            if (user == null) {
                cacheWasUsed = false;
    
                try {
    -->1            user = retrieveUser(username,
                            (UsernamePasswordAuthenticationToken) authentication);
                }
                catch (UsernameNotFoundException notFound) {
                    logger.debug("User '" + username + "' not found");
    
                    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);
    -->2        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);
    
            if (!cacheWasUsed) {
                this.userCache.putUserInCache(user);
            }
    
            Object principalToReturn = user;
    
            if (forcePrincipalAsString) {
                principalToReturn = user.getUsername();
            }
    
            return createSuccessAuthentication(principalToReturn, authentication, user);
        }
    

    其中:

    • -->1 处的代码表示调用方法 retrieveUser() 从数据库中加载用户信息。该方法代码如下:
    protected final UserDetails retrieveUser(String username,
                UsernamePasswordAuthenticationToken authentication)
                throws AuthenticationException {
            UserDetails loadedUser;
    
            try {
    -->1.1  loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            }
            catch (UsernameNotFoundException notFound) {
                if (authentication.getCredentials() != null) {
                    String presentedPassword = authentication.getCredentials().toString();
                    passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
                            presentedPassword, null);
                }
                throw notFound;
            }
            catch (Exception repositoryProblem) {
                throw new InternalAuthenticationServiceException(
                        repositoryProblem.getMessage(), repositoryProblem);
            }
    
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
    

    在上述代码中 -->1.1 处代码的意思为:调用成员变量 userDetailsService 的方法 loadUserByUsername() 加载数据层中的用户信息(是不是很熟悉)。

    • -->2 处的代码为调用方法 additionalAuthenticationChecks() 密码验证,该方法代码如下:
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                UsernamePasswordAuthenticationToken authentication)
                throws AuthenticationException {
            Object salt = null;
    
            if (this.saltSource != null) {
                salt = this.saltSource.getSalt(userDetails);
            }
    
            if (authentication.getCredentials() == null) {
                logger.debug("Authentication failed: no credentials provided");
    
                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
                        "Bad credentials"));
            }
    
            String presentedPassword = authentication.getCredentials().toString();
    ->2.1 if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
                    presentedPassword, salt)) {
                logger.debug("Authentication failed: password does not match stored value");
    
                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
                        "Bad credentials"));
            }
        }
    

    ->2.1 出代码调用了成员变量 passwordEncoder 的校验方法 isPasswordValid() 对用户密码进行验证。(是不是很熟悉)

    AuthenticationManager 流程图.png

    参考

    相关文章

      网友评论

        本文标题:AuthenticationManager 验证原理分析

        本文链接:https://www.haomeiwen.com/subject/oqlotxtx.html