过滤器FilterSecurityInterceptor
是一个请求处理过程中安全机制过滤器链中最后一个filter
,它执行真正的HTTP
资源安全控制,我们直接找到doFilter()方法。
FilterInvocation
你可以看成带有HttpServletRequest,HttpServletResponse,FilterChain属性的类,也就是过滤器上下文对象。
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor
implements Filter {
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 {
//1. 同一个请求的访问会直接放行
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain()
.doFilter(filterInvocation.getRequest(),
filterInvocation.getResponse());
return;
}
//1. 第一次处理该请求,加个标记
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//2. 过滤器传递前的操作:before
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
// filter链表的传递
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
}
finally {
// 刷新上下文,也就是SecurityContext
super.finallyInvocation(token);
}
//3. 过滤器传递后的操作:after
super.afterInvocation(token, null);
}
}
从上面源码我们可以看出当请求过来时会发生三件事:
1、请求是不是第一次来,如果是则修改标记;如果不是则放行
2、请求向下传递前处理
3、请求向下传递后处理
我们顺着上面继续看,去父类中找到beforeInvocation()方法:
public abstract class AbstractSecurityInterceptor{
// object == filterInvocation
protected InterceptorStatusToken beforeInvocation(Object object) {
// 1. 获取配置类中定义的用户信息
Collection<ConfigAttribute> attributes =
this.obtainSecurityMetadataSource().getAttributes(object);
// 2. 如果为空
if (CollectionUtils.isEmpty(attributes)) {
// 广播消息
// PublicInvocationEvent就是把filterInvocation封装进去
publishEvent(new PublicInvocationEvent(object));
return null;
}
// 2. 没有用户信息,抛出exception
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(this.messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
// 如果Authentication.isAuthenticated()返回false
// 或属性alwaysReauthenticate已设置为true需要再次检查
Authentication authenticated = authenticateIfRequired();
// 3. 关键授权方法
attemptAuthorization(object, attributes, authenticated);
// 4. 如果授权成功
if (this.publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// Attempt to run as a different user
// 4. 尝试不同的用户运行,权限前面如果有 "RUN_AS_"则会有结果
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
// 一般都为null
if (runAs != null) {
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
// 5. 返回结果
return new InterceptorStatusToken(SecurityContextHolder.getContext(),
false, attributes, object);
}
}
我们可以从上面代码看出:
1、先从配置读出定义好的权限,顺便检验下这个权限集合有没有问题,是不是为null,为null就广播一下
2、看看上下文有没有Authentication,需不需要再次认证
3、授权方法,授权成功就进行广播
4、尝试以其他用户身份运行,最后返回一个InterceptorStatusToken实例
InterceptorStatusToken
里面简单封装4个属性,如下:
public class InterceptorStatusToken {
// 上下文对象,其实也就一个Authentication属性
private SecurityContext securityContext;
// 权限集合
private Collection<ConfigAttribute> attr;
// 过滤器上下文对象,也就是FilterInvocation
private Object secureObject;
// 是否需要刷新
private boolean contextHolderRefreshRequired;
...
}
好,我们再继续往下看,我们的关键点在于attemptAuthorization()方法。
private void attemptAuthorization(Object object,
Collection<ConfigAttribute> attributes,
Authentication authenticated) {
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException ex) {
publishEvent(new AuthorizationFailureEvent(object, attributes,
authenticated, ex));
throw ex;
}
}
再往下寻找,知道AccessDecisionManager接口, 它常用的实现类是AffirmativeBased,我们发现它的父类存在一个投票的集合List<AccessDecisionVoter<?>> decisionVoters
。
public class AffirmativeBased {
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;
}
}
if (deny > 0) {
// 这里是个重点,因为我们常常去自定义它的处理器,也就是AccessDeniedHandler
throw new AccessDeniedException(
this.messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
checkAllowIfAllAbstainDecisions();
}
}
decide()中的功能实现比较简单,就遍历集合、调用投票类的投票方法。
我们再继续往下看,AccessDecisionVoter接口-投票类的顶层接口,它在其中定义了三种状态(如下) ,它常用的实现有RoleVoter、WebExpressionVoter、RoleHierarchyVoter,vote()方法中的object是上下文对象。
public interface AccessDecisionVoter<S> {
// 赞成
int ACCESS_GRANTED = 1;
// 弃权
int ACCESS_ABSTAIN = 0;
// 反对
int ACCESS_DENIED = -1;
}
先来看下RoleVoter类:就很简单
public class RoleVoter {
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
if (authentication == null) {
return ACCESS_DENIED;
}
// 默认为 0
int result = ACCESS_ABSTAIN;
Collection<? extends GrantedAuthority> authorities =
extractAuthorities(authentication);
// 遍历权限集合
for (ConfigAttribute attribute : attributes) {
// 看权限是不是ROLE_开头的
if (this.supports(attribute)) {
result = ACCESS_DENIED;
// 再遍历认证好的用户信息中,看是否匹配
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
}
再来看下WebExpressionVoter类:这里主要是基于表达式的权限认证,它的语法都是SpEL,不知道的同学可以点击这里,它分两步走:先根据attributes来创建WebExpressionConfigAttribute对象,再根据Authentication来创建EvaluationContext对象,最后再调用方法看是否匹配。
public int vote(Authentication authentication, FilterInvocation filterInvocation,
Collection<ConfigAttribute> attributes) {
//
WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);
if (webExpressionConfigAttribute == null) {
return ACCESS_ABSTAIN;
}
EvaluationContext ctx = webExpressionConfigAttribute.
postProcess(this.expressionHandler.
createEvaluationContext(authentication, filterInvocation), filterInvocation);
boolean granted = ExpressionUtils.
evaluateAsBoolean(webExpressionConfigAttribute.
getAuthorizeExpression(), ctx);
if (granted) {
return ACCESS_GRANTED;
}
return ACCESS_DENIED;
}
好了,到这里,权限认证的源码的流程也结束了,权限认证的流程其实很简单:去配置获取权限集合,拿到Authentication,将这两个东西交个投票器进行投票,就这样。