FilterSecurityInterceptor
FilterSecurityInterceptor是一个用于授权的过滤器 位于security过滤链的最尾部
其继承了AbstractSecurityInterceptor抽象类
首先来看看FilterSecurityInterceptor的doFilter方法 由于过滤器的特性 doFilter是过滤器执行的入口
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/**
* 这个FilterInvocation对象它的功能很简单,
* 就是将上一个过滤器传递过滤的request,response,chain复制保存到FilterInvocation里
* 也有人说FilterInvocation起到了解耦作用 避免了与其他过滤器使用相同的引用变量
*/
FilterInvocation fi = new FilterInvocation(request, response, chain);
//执行方法invoke
this.invoke(fi);
}
public void invoke(FilterInvocation fi)
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//这里的if用作安全检查 如果当前请求不是第一次请求 且拥有_spring_security_filterSecurityInterceptor_filterApplied属性
//则可以直接放行 不进行鉴权
if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
//直接放行
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} else {
//否则进行安全检查 observeOncePerRequest默认为true
if (fi.getRequest() != null && this.observeOncePerRequest) {
//为当前请求设置属性 这里设置完以后 之后同样的请求就可以不进行鉴权直接访问了
fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
//鉴权入口方法 调用了父类AbstractSecurityInterceptor的beforeInvocation方法 ★
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行过滤链
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
//清空SecurityContext上下文内容 将原来的上下文内容恢复
/**
* 因为在beforeInvocation里可能调用RunAsManager的方法替换了验证身份
* 导致SecurityContext里的内容与原来不一致
* 以上代码执行失败与否 这个方法都执行
*/
super.finallyInvocation(token);
}
super.afterInvocation(token, (Object)null);
}
}
beforeInvocation
protected InterceptorStatusToken beforeInvocation(Object object) {
...
//if条件仅是判断 object是不是FilterInvocation类型的对象
if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
//这里是抛异常代码 已省略
} else {
//这里就是拿到我们配置的权限信息 如什么角色可以访问什么方法等
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if (attributes != null && !attributes.isEmpty()) {
//如果当前security上下文中没有用户认证信息就抛异常
if (SecurityContextHolder.getContext().getAuthentication() == null) {
//这里抛 认证信息没找到的异常 已省略
}
//authenticateIfRequired方法用于认证用户信息 如果当前用户信息还没认证 则会在这里进行认证并放入security上下文后返回认证对象
Authentication authenticated = this.authenticateIfRequired();
try {
//重点 这里就是用于鉴权的方法 如果没抛出异常则说明鉴权通过 详解请看下一小节
this.accessDecisionManager.decide(authenticated, object, attributes);
} catch (AccessDeniedException var7) {
//略
}
//告诉spring容器中其他bean 鉴权成功的信息
if (this.publishAuthorizationSuccess) {
this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
/**
* 在极少数情况下,用户可能希望使用不同的Authentication替换SecurityContext中的Authentication
* 这在合理的异常情况下可能很有用,例如服务层方法需要调用远程系统并呈现不同的身份。
*/
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
if (runAs == null) {
//返回一个没有改变过的SecurityContext
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
} else {
//如果替换了用户认证信息 获得原来的securityContext上下文对象
SecurityContext origCtx = SecurityContextHolder.getContext();
//然后新建一个SecurityContext上下文
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
//在新建的上下文对象中放入替换的认证信息
SecurityContextHolder.getContext().setAuthentication(runAs);
//将原来的上下文对象及刷新上下文标志 权限list等信息返回
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
//rejectPublicInvocations默认为false 当为true时将拒绝所有没有带权限list的资源的访问
} else if (this.rejectPublicInvocations) {
//抛异常
} else {
//告诉spring容器中其他bean 本次请求没有通过鉴权
this.publishEvent(new PublicInvocationEvent(object));
return null;
}
}
}
AccessDecisionManager
AccessDecisionManager是一个接口,它负责鉴定用户是否有访问对应资源(方法或URL)的权限。
它声明了三个方法,除了第一个鉴权方法以外,还有两个是辅助性的方法,其作用都是甄别 decide方法中参数的有效性。
public interface AccessDecisionManager {
void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;
boolean supports(ConfigAttribute var1);
boolean supports(Class<?> var1);
}
AccessDecisionManager 共有三个实现类 内部采用投票的机制调用投票方法来决定当前请求是否通过鉴权
- AffirmativeBased 一票赞成就可以通过 默认实现类
- UnanimousBased 一票反对就不能通过
- ConsensusBased 少数服从多数
现在来看看AffirmativeBased默认实现类的实现方法decide,使用此方法,将根据授权决策轮询一系列 AccessDecisionVoter 实现。
public void decide(Authentication authentication, Object object, Collection configAttributes)
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
//获取所有投票器
Iterator var5 = this.getDecisionVoters().iterator();
while(var5.hasNext()) {
//通过迭代器获取一个投票类
AccessDecisionVoter voter = (AccessDecisionVoter)var5.next();
//调用投票方法 返回投票结果
int result = voter.vote(authentication, object, configAttributes);
//日志代码 略
switch(result) {
case -1:
++deny;
break;
case 1:
//如果投票结果通过 则返回
return;
}
}
//AccessDecisionManager 根据其对投票的评估来决定是否抛出 AccessDeniedException
if (deny > 0) {
throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
} else {
this.checkAllowIfAllAbstainDecisions();
}
}
到此 鉴权结束 感谢观看