授权是在用户认证通过后,对访问资源的权限进行检查的过程。
Spring Security使用标准 Filter建立了对 web请求的拦截,最终实现对资源的授权访问。
- Spring Security过滤器链加载执行流程源码分析请查看:https://blog.csdn.net/qq_42402854/article/details/122205790
关于授权,这里查看几个重要的 Filter 的处理逻辑。
一、SecurityContextPersistenceFilter
针对 ServletRequest进行了一次包装,使得 request具有更加丰富的 API。
查看 doFilter方法:
- 通过repo.loadContext从请求中获取session,然后将 session信息保存在context中,方便后面 filter直接获取当前的用户信息。
- 通过 SecurityContextHolder.setContext(contextBeforeChainExecution); 设置 context信息到contextHolder中(将context信息放在了ThreadLocal中,线程安全的)。
1)repo.loadContext方法
2)setContext方法
二、AnonymousAuthenticationFilter
Spring Security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
AnonymousAuthenticationFilter过滤器是在 UsernamePasswordAuthenticationFilter等过滤器之后,如果它前面的过滤器都没有认证成功,即认证信息为空,AnonymousAuthenticationFilter会创建一个匿名用户(一个 Authenticaiton 的匿名实现类 AnonymousAuthenticationToken
)存入到当前 SecurityContextHolder中。
查看 doFilter方法:
- 判断 SecurityContextHolder中Authentication为否为空;
- 如果空则为当前的 SecurityContextHolder中添加一个匿名的 AnonymousAuthenticationToken(
用户名为 anonymousUser
的 AnonymousAuthenticationToken)
三、ExceptionTranslationFilter
ExceptionTranslationFilter异常处理过滤器,用来处理在系统认证授权过程中抛出的异常(也就是下一个 FilterSecurityInterceptor过滤器)。
ExceptionTranslationFilter过滤器主要是拦截处理 AuthenticationException
和 AccessDeniedException
异常并添加到HTTP响应中。其他异常它会捕获 Filter处理的异常,抛给下一个 Filter去处理。
查看 doFilter方法:
四、FilterSecurityInterceptor
FilterSecurityInterceptor过滤器
是认证授权过滤器链中最后一个过滤器,获取所配置资源访问的授权信息,根据 SecurityContextHolder中存储的用户信息来决定其是否有权限。
该过滤器之后就交由 Spring MVC,访问到我们的controller方法。
查看 doFilter方法:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
this.invoke(fi);
}
/**
* doFilter实际执行的方法
* @param filterInvocation 封装了request response 过滤器链的对象
*/
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
// 1. 如果已经执行过该过滤器,直接放行
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
return;
}
// first time this request being called, so perform security checking
// 2. 第一次调用这个请求,所以执行安全检查
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
// 3. 在request中添加__spring_security_filterSecurityInterceptor_filterApplied = true,表示执行了该过滤器
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 4. 前置访问控制处理
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
// 5. 放行
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
super.finallyInvocation(token);
}
// 6. 后置处理
super.afterInvocation(token, null);
}
比较重要的方法
- beforeInvocation
- finallyInvocation
- afterInvocation
1、查看 beforeInvocation方法
进入父类 AbstractSecurityInterceptor
的 beforeInvocation方法进行处理,最终返回一个 InterceptorStatusToken对象
,它就是 Spring Security处理鉴权的入口。
protected InterceptorStatusToken beforeInvocation(Object object) {
// 1. 判断object是不是FilterInvocation
if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(...);
} else {
// 2. 获取配置的访问控制规则 any request => authenticated ,没有配置,return null
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
...
// 3. 获取Authentication对象
Authentication authenticated = this.authenticateIfRequired();
try {
// 4. 进行授权判断(重点)
this.accessDecisionManager.decide(authenticated, object, attributes);
} catch (AccessDeniedException var7) {
this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));
throw var7;
}
if (debug) {
this.logger.debug("Authorization successful");
}
// 5. 发布授权成功
if (this.publishAuthorizationSuccess) {
this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// 6. 对Authentication进行再处理,这里没有处理,直接返回null
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
if (runAs == null) {
if (debug) {
this.logger.debug("RunAsManager did not change Authentication object");
}
// 7. 返回 InterceptorStatusToken
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
} else {
if (debug) {
this.logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// 7. 返回 InterceptorStatusToken
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
} else if (this.rejectPublicInvocations) {
throw new IllegalArgumentException(...);
} else {
if (debug) {
this.logger.debug("Public object - authentication not attempted");
}
this.publishEvent(new PublicInvocationEvent(object));
return null;
}
}
}
1.1 AffirmativeBased授权处理器
AccessDecisionManager 是如何授权的?
Spring Security默认使用 AffirmativeBased
实现 AccessDecisionManager 的 decide 方法来实现授权。它会调用授权管理器进行决策,当失败发生异常时,会爆出异常。
调用 AccessDecisionVoter 进行vote(投票)
- 只要有投通过(ACCESS_GRANTED)票,则直接判为通过。
- 如果没有投通过则 ++deny ,
- 最后判断if(deny>0 抛出AccessDeniedException(未授权)
1.2 WebExpressionVoter.vote()
这里简单查看 WebExpressionVoter.vote()方法:
public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {
assert authentication != null;
assert fi != null;
assert attributes != null;
// 1. 获取http配置项
WebExpressionConfigAttribute weca = this.findConfigAttribute(attributes);
// 2. 没有配置规则,弃权
if (weca == null) {
return 0;
} else {
// 3. 对EL表达式进行处理
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, fi);
ctx = weca.postProcess(ctx, fi);
// 5. 符合条件,则投赞成票,否则反对票
return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? 1 : -1;
}
}
投票完成,回到 beforeInvocation方法,最后返回 InterceptorStatusToken对象。
2、查看 finallyInvocation方法
授权成功处理:没有抛出异常,则认为授权通过,FilterSecurityInterceptor会进入 finallyInvocation方法。
这个方法主要是判断需不需要重新设置 SecurityContext内容,这里没有配置,直接跳过。
3、查看 afterInvocation方法
接下来进入 afterInvocation方法,再次调用了 finallyInvocation方法,然后查询是否还有决策后置处理器,如果有,再次进行决策。
最后的最后,才代表授权成功,就交由 Spring MVC,访问到我们的 controller方法了。
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
if (token == null) {
return returnedObject;
} else {
this.finallyInvocation(token);
if (this.afterInvocationManager != null) {
try {
returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(), token.getSecureObject(), token.getAttributes(), returnedObject);
} catch (AccessDeniedException var5) {
AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), token.getSecurityContext().getAuthentication(), var5);
this.publishEvent(event);
throw var5;
}
}
return returnedObject;
}
}
– 求知若饥,虚心若愚。