『Spring Security』(三) 默认认证授权总体流程及源码解读

认证授权总流程

先来一张自己梳理的,Spring Security 认证授权的全流程图。这一章,主要根据流程,简单走读梳理源码。

image-20201110150437270

认证源码分析

AbstractAuthenticationProcessingFilter,这个类是认证流程的入口。

AbstractAuthenticationProcessingFilter 根据 Request 创建 Authentication

Authentication 具体类型, 由实际使用的 AbstractAuthenticationProcessingFilter 的子 类决定。 常见的有: UsernamePasswordAuthenticationToken

默认情况下,使用 UsernamePasswordAuthenticationFilter类,所以对应的AuthenticationUsernamePasswordAuthenticationToken

UsernamePasswordAuthenticationFilter#attemptAuthentication//……表示删除部分次要逻辑。

// UsernamePasswordAuthenticationFilter#attemptAuthentication,`//……`表示删除部分次要逻辑。
public Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException {
    // ……
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        // ……
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);
 
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
 
        return this.getAuthenticationManager().authenticate(authRequest);
    }
复制代码

我们可以看到,上面最后一句,调用了AuthenticationManager。通过官方文档,我们知道ProviderManagerAuthenticationManager的默认实现。所以我们需要到ProviderManager中。

支持 UsernamePasswordAuthenticationToken 的 provider 是 DaoAuthenticationProviderDaoAuthenticationProvider继承自AbstractUserDetailsAuthenticationProvider

ProviderManager#authenticate

 
  public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
    // 获取 Autentication 的类型,这是我们实际是 UsernamePasswordAuthenticationToken
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
    // 获取所有 AuthenticationProvider
        for (AuthenticationProvider provider : getProviders()) {
      // 判断 provider 是否支持该 Autentication
      // 这里 DaoAuthenticationProvider 通过验证
            if (!provider.supports(toTest)) {
                continue;
            }
            // ……
            try {
                // 执行认证 AbstractUserDetailsAuthenticationProvider#authenticate
                result = provider.authenticate(authentication);
                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
            // ……  
        }
        // …………
        if (result != null) {
            if (eraseCredentialsAfterAuthentication
                    && (result instanceof CredentialsContainer)) {
                // Authentication is complete. Remove credentials and other secret data
                // from authentication
                ((CredentialsContainer) result).eraseCredentials();
            }
      //……………………………………
      //……………………………………
    }
 
复制代码

AbstractUserDetailsAuthenticationProvider#supports、authenticate

public boolean supports(Class<?> authentication) {
    return (UsernamePasswordAuthenticationToken.class
            .isAssignableFrom(authentication));
}
 
public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        // ……
        // 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 {
        // 缓存中没有,调用 retrieveUser 方法获取用户
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
        // ……
            }
 
            // ……
      // ……
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }
复制代码

DaoAuthenticationProvider#retrieveUser、createSuccessAuthentication

protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
      // 上述逻辑,如果内存中没有用户,就调用UserDeatilsService#loadUserByUsername 获取用户
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        // …………
    }
 
@Override
    protected Authentication createSuccessAuthentication(Object principal,
            Authentication authentication, UserDetails user) {
    // 验证密码
        boolean upgradeEncoding = this.userDetailsPasswordService != null
                && this.passwordEncoder.upgradeEncoding(user.getPassword());
        if (upgradeEncoding) {
            String presentedPassword = authentication.getCredentials().toString();
            String newPassword = this.passwordEncoder.encode(presentedPassword);
            user = this.userDetailsPasswordService.updatePassword(user, newPassword);
        }
        return super.createSuccessAuthentication(principal, authentication, user);
    }
 
// super.createSuccessAuthentication
protected Authentication createSuccessAuthentication(Object principal,
            Authentication authentication, UserDetails user) {
        // 创建 UsernamePasswordAuthenticationToken 返回
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                principal, authentication.getCredentials(),
                authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
 
        return result;
    }
复制代码

授权源码分析

授权的入口是 FilterSecurityInterceptor

FilterSecurityInterceptor

//……
private FilterInvocationSecurityMetadataSource securityMetadataSource;
// ……
public void invoke(FilterInvocation fi) throws IOException, ServletException {
          // ……
            InterceptorStatusToken token = super.beforeInvocation(fi);
            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }
            super.afterInvocation(token, null);
        }
    }
// ……
复制代码

这说明,Spring Security 默认的SecurityMetadataSource FilterInvocationSecurityMetadataSource

我们观察到,super.beforeInvocation(),进入其父类继续走流程。

AbstractSecurityInterceptor

protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        // ……
    // 此处调用 FilterInvocationSecurityMetadataSource  , 获取访问当前路径需要的权限集合
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);
    // 如果为空,则不继续进行鉴权,直接返回
        if (attributes == null || attributes.isEmpty()) {
            // ……
            publishEvent(new PublicInvocationEvent(object));
            return null; // no further work post-invocation
        }
        // ……
    // 如果没有认证,抛出异常
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage(
                    "AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"),
                    object, attributes);
        }
 
     // ……
 
        // 此处调用 accessDecisionManager 授权,若权限不足则抛出异常。
        try {
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));
            throw accessDeniedException;
        }
 
    // ……
    // ……
    }
复制代码

AccessDecisionManager 是一个接口。我们可以从日志中得知,Spring Security 默认使用得是其实现类 AffirmativeBased,并且还使用到了 WebExpressionVoter

o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@bb39abe, returned: -1
复制代码

AffirmativeBased#decide

public void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
        int deny = 0;
 
        for (AccessDecisionVoter voter : getDecisionVoters()) {
      // 处理授权
            int result = voter.vote(authentication, object, configAttributes);
            // ……
            switch (result) {
                  case AccessDecisionVoter.ACCESS_GRANTED:
                      return;
                  case AccessDecisionVoter.ACCESS_DENIED:
                      deny++;
                      break;
                  default:
                      break;
            }
        }
     // 上面进行授权操作,授权失败 deny 就加一。到此处如果大于0就抛出异常,否则就正常进行。
        if (deny > 0) {
            throw new AccessDeniedException(messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }
     // ……
    }
复制代码

实际判断授权的操作,是在voter.vote()中进行的。从日志中得知,是由其实现类WebExpressionVoter处理。

WebExpressionVoter#vote、findConfigAttribute

public int vote(Authentication authentication, FilterInvocation fi,            Collection<ConfigAttribute> attributes) {        assert authentication != null;        assert fi != null;        assert attributes != null;       // attributes 转化为 WebExpressionConfigAttribute        WebExpressionConfigAttribute weca = findConfigAttribute(attributes);     // 如果null,默认是通过的        if (weca == null) {            return ACCESS_ABSTAIN;        }     // 创建判断环境        EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,                fi);        ctx = weca.postProcess(ctx, fi);    // 执行判定        return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED                : ACCESS_DENIED;    }   // 把 attribute 转化为 WebExpressionConfigAttribute,如果没匹配到就返回null    private WebExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {        for (ConfigAttribute attribute : attributes) {            if (attribute instanceof WebExpressionConfigAttribute) {                return (WebExpressionConfigAttribute) attribute;            }        }        return null;    }
复制代码

就此鉴权也就结束了。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享