美文网首页
Spring Security 认证流程源码级详解

Spring Security 认证流程源码级详解

作者: 寂静的春天1988 | 来源:发表于2020-07-24 10:18 被阅读0次
    image.png

    首先进入了UsernamePasswordAuthenticationFilter

    image.png

    拿到了用户名和密码,创建了一个UsernamePasswordAuthenticationToken对象。


    image.png

    首先调用的是这个构造方法:
    1、先将父类的构造方法设置为null。


    父类的构造函数.png
    父类的构造函数需要传用户的权限,这时还没有通过验证,自然也得不到用户权限,就传了一个null值

    2、设置了本地的用户名和密码变量
    3、setAuthenticated(false); 表示当前的这些身份信息是否通过验证,自然也是false

    return this.getAuthenticationManager().authenticate(authRequest);

    AuthenticationManager是用来管理AuthenticationProvider,AuthenticationProvider是真正处理验证逻辑的。

    进入到AuthenticationManager看看它的authenticate方法:

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

    先看这几行代码

    Class<? extends Authentication> toTest = authentication.getClass();
    for (AuthenticationProvider provider : getProviders()) {
                if (!provider.supports(toTest)) {
                    continue;
                }
    

    这里将外面传过来的AuthenticationToken,用supports方法校验一下,当前的AuthenticationProvider是否支持这个AuthenticationToken。

    return (UsernamePasswordAuthenticationToken.class
                    .isAssignableFrom(authentication));
    

    supports实现也很简单,判断了传过来的token是否是自己需要的token类,因为不同的过滤器需要的token类是不同的。

    简而言之:就是通过传来的AuthenticationToken来匹配上了相应的provider 类。

    result = provider.authenticate(authentication);
    这里通过provider调用了真正的验证方法。

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

    user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);

    看着这行代码的内部实现

    protected final UserDetails retrieveUser(String username,
                UsernamePasswordAuthenticationToken authentication)
                throws AuthenticationException {
            UserDetails loadedUser;
    
            try {
                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;
        }
    

    loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    可以看到这里就是通过UserDetailsService的loadUserByUsername方法,通过用户名来查询到UserDetails。之前我们在MyUserDetailsService中也重写了loadUserByUsername方法。

    接着往下走:

        preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,
                        (UsernamePasswordAuthenticationToken) authentication);
    

    拿到user对象后,做了一个预检查操作
    preAuthenticationChecks.check(user);的具体实现

    
        private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
            public void check(UserDetails user) {
                if (!user.isAccountNonLocked()) {
                    logger.debug("User account is locked");
    
                    throw new LockedException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.locked",
                            "User account is locked"));
                }
    
                if (!user.isEnabled()) {
                    logger.debug("User account is disabled");
    
                    throw new DisabledException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.disabled",
                            "User is disabled"));
                }
    
                if (!user.isAccountNonExpired()) {
                    logger.debug("User account is expired");
    
                    throw new AccountExpiredException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.expired",
                            "User account has expired"));
                }
            }
        }
    

    这里是对UserDetails中之前的 是否冻结,是否锁定...的3个方法,进行校验。

    additionalAuthenticationChecks(user,
                        (UsernamePasswordAuthenticationToken) authentication);
    

    这里是用passwordEncoder来看密码的加密解密是否匹配。

    后检查:检查4个方法里的最后一个。

    postAuthenticationChecks.check(user);
    
    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
            public void check(UserDetails user) {
                if (!user.isCredentialsNonExpired()) {
                    logger.debug("User account credentials have expired");
    
                    throw new CredentialsExpiredException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
                            "User credentials have expired"));
                }
            }
        }
    
    

    当所有校验完毕:

    return createSuccessAuthentication(principalToReturn, authentication, user);
    

    创建一个成功的AuthenticationToken

        protected Authentication createSuccessAuthentication(Object principal,
                Authentication authentication, UserDetails user) {
            // Ensure we return the original credentials the user supplied,
            // so subsequent attempts are successful even with encoded passwords.
            // Also ensure we return the original getDetails(), so that future
            // authentication events after cache expiry contain the details
            UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                    principal, authentication.getCredentials(),
                    authoritiesMapper.mapAuthorities(user.getAuthorities()));
            result.setDetails(authentication.getDetails());
    
            return result;
        }
    
        public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
                Collection<? extends GrantedAuthority> authorities) {
            super(authorities);
            this.principal = principal;
            this.credentials = credentials;
            super.setAuthenticated(true); // must use super, as we override
        }
    

    这是调用的是三个参数的构造方法了。

    这里也可以看出super(authorities);和super.setAuthenticated(true);
    通过验证了,所以将权限和setAuthenticated设置为true。

    这是UsernamePasswordAuthenticationFilter得到就是验证成功的UserDetails了。

    问题:认证结果如何在多个请求之前共享?
    将AuthenticationToken封装到了SecurityContext,SecurityContext只是包装了AuthenticationToken,重写了equals和hashCode方法保证了唯一性。然后将SecurityContext放进了SecurityContextHolder,SecurityContextHolder相当于一个ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();
    这样在整个请求的过程中可以通过SecurityContextHolder拿到AuthenticationToken。

    请求进入SecurityContextPersistenceFilter,会先检查session中是否有SecurityContext,如果有就放入线程中,经过验证过滤器后又会回到SecurityContextPersistenceFilter,检查线程中是否有SecurityContext,如果有就放入session中。
    这样不同的请求就共享了一个session。

    问题:获取认证用户信息

    通过SecurityContextHolder获得。

    public class UserController {
        
        @RequestMapping("/me")
        public Object getCurrentUser() {
            
            return SecurityContextHolder.getContext().getAuthentication();
        }
    

    简化写法:

        @RequestMapping("/me")
        public Object getCurrentUser(Authentication authentication) {
            
            return authentication;
        }
    

    只想获取UserDetails

        @RequestMapping("/me")
        public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
            
            return userDetails;
        }
    

    相关文章

      网友评论

          本文标题:Spring Security 认证流程源码级详解

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