当我们成功登录,获取 access_token
,即可使用该token来访问有权限的接口。如上文所讲, JwtAuthenticationFilter
将 access_token
转化为系统可识别的 Authentication
放入安全上下文,
则来到最后一个过滤器 FilterSecurityInterceptor
,该过滤则是判断请求是否拥有权限。
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) { // filter already applied to this request and user wants us to observe // once-per-request handling, so don't re-do security checking fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { // first time this request being called, so perform security checking if (fi.getRequest() != null && observeOncePerRequest) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } // 请求之前的工作,也就是真正的权限认证的过程 InterceptorStatusToken token = super.beforeInvocation(fi); try { // 请求真正的controller fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.finallyInvocation(token); } // 请求后的工作 super.afterInvocation(token, null); } } } 复制代码
FilterSecurityInterceptor的主体方法依旧在doFilter中,而其中主要的方法为invoke(),大约分为三个步骤:
首先来看看beforeInvocation()
abstract class AbstractSecurityInterceptor { protected InterceptorStatusToken beforeInvocation(Object object) { // 获取目标url的权限内容,这些内容可以从configuration中获取也可以用MetadataSource中获取 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object); // ……省略 Authentication authenticated = authenticateIfRequired(); // Attempt authorization try { // AccessDecisionManager用于验证Authentication中的权限和目标url所需权限是否匹配,如果不匹配则抛出AccessDeniedException异常 this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); // 下一步则是生成InterceptorStatusToken,用于AfterInvocation步骤。有兴趣可以自己看 if (runAs == null) { // no further work post-invocation return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); } else { SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); // need to revert to token.Authenticated post-invocation return new InterceptorStatusToken(origCtx, true, attributes, object); } } } 复制代码
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
获取目标url所需要的权限,
该类实现 FilterInvocationSecurityMetadataSource
接口的方法。而配置url权限也可以从 WebSecurityConfig
中的configuration方法配置。 this.accessDecisionManager.decide(authenticated, object, attributes);
判断 Authentication
中的权限目标url所需权限是否匹配,匹配则通过;不匹配则抛出 AccessDeniedException
异常。
该方法来自 AbstractAccessDecisionManager
的实现类,系统默认实现为 AffirmativeBased
。 new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
实现 InterceptorStatusToken
并返回,包括参数中的信息,如安全上下文、目标url所需权限、原始的访问请求。 之后则访问目标Controller,获取真正的请求内容。
当我们启用了 @PreAuthorize()
、 @PostAuthorize()
注解的时候则会 AfterInvocationManger
,进而有以下验证逻辑。
abstract class AbstractSecurityInterceptor { protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) { if (token == null) { // public object return returnedObject; } finallyInvocation(token); // continue to clean in this method for passivity if (afterInvocationManager != null) { // Attempt after invocation handling try { returnedObject = afterInvocationManager.decide(token.getSecurityContext() .getAuthentication(), token.getSecureObject(), token .getAttributes(), returnedObject); } catch (AccessDeniedException accessDeniedException) { AuthorizationFailureEvent event = new AuthorizationFailureEvent( token.getSecureObject(), token.getAttributes(), token .getSecurityContext().getAuthentication(), accessDeniedException); publishEvent(event); throw accessDeniedException; } } return returnedObject; } } 复制代码
以下代码则是包含 AfterInvocationManager
具体的实现。
public class GlobalMethodSecurityConfiguration { protected AfterInvocationManager afterInvocationManager() { if (prePostEnabled()) { AfterInvocationProviderManager invocationProviderManager = new AfterInvocationProviderManager(); ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice( getExpressionHandler()); PostInvocationAdviceProvider postInvocationAdviceProvider = new PostInvocationAdviceProvider( postAdvice); List<AfterInvocationProvider> afterInvocationProviders = new ArrayList<>(); afterInvocationProviders.add(postInvocationAdviceProvider); invocationProviderManager.setProviders(afterInvocationProviders); return invocationProviderManager; } return null; } } 复制代码
实现 FilterInvocationSecurityMetadataSource
,用于启动时加载url所需的权限,这样就不用在configuration或者注解中将目标url权限‘写死’。
可以参照本例所写的实现 MyFilterInvocationSecurityMetadataSource
。
重载 AbstractAccessDecisionManager
,根据业务需要重写,请求目标权限和Authentication中权限的验证过程.
举个例子,Spring Security中默认的RBAC,即,权限认证都是根据角色判断,固定角色只能访问固定接口。
现在我们需要ACL权限模型,用户A权限为1,用户B权限为5,用户C权限为9,接口a需要权限6,则用户C可以访问,
而用户A、B不能访问,就是说权限大的可以访问权限小的接口,如果需要改变权限模型则重载该类即可。
授权过程主要有哪些?
FilterInvocationSecurityMetadataSource
接口的实现类获取。 Authentication
中的权限是否匹配,在 AbstractAccessDecisionManager
的实现类中比较。