Spring Security AuthenticationManager 验证原理分析

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

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值