Spring Security的权限管理(下)

122 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第17天,点击查看活动详情

Spring Security授权原理分析

\qquad在之前我们提到过,授权最重要的三个类是AccessDecisionManager,AccesDecisionVoter以及ConfigAttribute。ConfigAttribute类在Spring Security 中的主要作用是存储用户请求资源(通常是一个接口或者一个方法)时所需要的角色,在ConfigAttribute中只有一个getAttribute方法,该方法返回一个String 字符串,就是角色的名称。一般来说,角色名称都带有一个ROLE_前缀,投票器AccessDecisionVoter 所做的事情,其实就是比较用户所具备的角色和请求某个资源所需的ConfigAtuibute之间的关系。
\qquadAccesDecisionVoter 和 AccessDecisionManager 都有众多的实现类,在AccessDecisionManager 中会换个遍历AccessDecisionVoter,进而决定是否允许用户访问,因而AaccesDecisionVoter 和AccessDecisionManager 两者的关系就像AuthenticationProvider 和 ProviderManager 的关系。

\qquad请求一个资源时,所经历的流程大致如下,最重要的是过滤器FilterSecurityInterceptor: 授权流程.png

FilterSecurityInterceptor

\quad当我们在配置的方法configure(HttpSecurity)中调用http. authorizeRequests(开启URL路径拦截规则配置时,就会通过AbstractInterceptUrlConfigurer#configure方法将FilterSecurityInterceptor添加到SpringSecurity过滤器链中。一个过滤器最重要的就是doFilter方法:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   invoke(new FilterInvocation(request, response, chain));
}

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
   if (isApplied(filterInvocation) && this.observeOncePerRequest) {
      filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
      return;
   }
   if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
      filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
   }
   InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
   try {
      filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
   }
   finally {
      super.finallyInvocation(token);
   }
   super.afterInvocation(token, null);
}

\qquad在doFilter方法中,直接调用了本类的invoke方法。invoke方法的大致流程如下,如果当前过滤器已经执行过了,则继续执行剩下的过滤器,否则就调用父类的beforeInvocation方法进行权限校验,校验通过后继续执行剩余的过滤器,然后在finally 代码块中调用父类的finallyInvocation 方法,最后调用父类的afterInvocation方法。可以看到,前置处理器和后置处理器都是在invoke方法中触发的。

protected InterceptorStatusToken beforeInvocation(Object object) {
    Assert.notNull(object, "Object was null");
    if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
        throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + this.getSecureObjectClass());
    } else {
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
        if (CollectionUtils.isEmpty(attributes)) {
            Assert.isTrue(!this.rejectPublicInvocations, () -> {
                return "Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. This indicates a configuration error because the rejectPublicInvocations property is set to 'true'";
            });
            if (this.logger.isDebugEnabled()) {
                this.logger.debug(LogMessage.format("Authorized public object %s", object));
            }

            this.publishEvent(new PublicInvocationEvent(object));
            return null;
        } else {
            if (SecurityContextHolder.getContext().getAuthentication() == null) {
                this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
            }

            Authentication authenticated = this.authenticateIfRequired();
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));
            }

            this.attemptAuthorization(object, attributes, authenticated);

            。。。。。。。
        }
    }
}

\qquad该方法首先调用obtainSecurityMetadataSource方法获取SecurityMetadataSource对象,然后调用其getAttributes 方法获取受保护对象所需要的权限。然后到SecurityContextHolder中查看当前用户的认证信息是否存在。紧接着就是调用authenticateIfRequired方法检查当前用户是否已经登录。最重要的就是调用accessDecisionManager的decide方法进行决策,该方法中会调用投票器进行投票,如果该方法执行抛出异常,则说明权限不足。

private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) {
    try {
        this.accessDecisionManager.decide(authenticated, object, attributes);
    } catch (AccessDeniedException var5) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, attributes, this.accessDecisionManager));
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes));
        }

        this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var5));
        throw var5;
    }
}

\qquad在这个决策管理类的decide方法里面,主要就是遍历各个投票器,对各投票器的结果进行处理,获得最终结果,也就是这个用户到底有没有资格去访问请求的资源。