1.作用
这个主要是针对http请求进行权限校验
权限校验需要注意的几点:
1.用户需要先认证
2.获取资源所需授权
3.获取用户被授权信息
4.授权校验
5.授权成功处理
6.授权失败处理
2.直接开撸
2.1 参数封装
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 先构建了一个封装类,将request,response,chain放入。
// 这种设计的目的一般是因为这三个参数需要多处传递
// 其二就是FilterInvocation 会对着三个参数的数据获取进行包装和修饰(就是封装成你想要的)
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
2.2 核心代码
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);
}
// 核心方法1:通过FilterInvocation来获取用户的SecurityContext,
// 授权信息(ConfigAttribute),授权对象(secureObject)等封装类
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
// 核心方法2:基于用户授权信息封装类判断用户是否能通过授权
super.finallyInvocation(token);
}
// 核心方法3:
super.afterInvocation(token, null);
}
}
3.先看看核心方法1 beforeInvocation
1.检查当前被授权对象跟过滤器是否匹配
当前一共有两种过滤器来进行授权,一种是FilterSecurityInterceptor
,一种是MethodSecurityInterceptor
。
然后这两种都提供了各自的封装
FilterInvocation
: 主要是针对url 授权
MethodInvocation
: 主要是针对方法 授权
这里就是检查传入的Invocation和当前过滤器能处理的Invocation是否一致。
2.获取权限ConfigAttribute
当没有获取配置的权限信息时,那就是说,不需要鉴权了。
那如何通过FilterInvocation
去获取权限信息的呢?在DefaultFilterInvocationSecurityMetadataSource
中通过RequestMatcher
去匹配url来获取对应的权限。
那DefaultFilterInvocationSecurityMetadataSource的信息都是写什么呢?我通过debug发现,在FilterSecurityInterceptor中使用的FilterInvocationSecurityMetadataSource的实现类是DefaultFilterInvocationSecurityMetadataSource的子类FilterInvocationSecurityMetadataSource。
下面看看FilterInvocationSecurityMetadataSource的具体内容。
3.FilterInvocationSecurityMetadataSource 基于url的权限元数据信息
通过debug发现ExpressionBasedFilterinvocationSecurityMetadataSource
是默认的FilterInvocationSecurityMetadataSource
的默认实现。
fullAuthenticated()
: 需要完全授权,也就是rememberMe的这种不能访问
denyAll()
: url匹配的都不允许访问
这种就是你针对spring security的全局权限配置信息。
4.检查认证信息
5.授权校验
AccessDecisionManager
这个权限校验体系很有意思。
针对一个资源(图片,js等静态资源,http请求)Spring Security设定了多种多种权限控制。
所以在设计AccessDecisionManager
时,它只是用于对授权的汇总和最终决策。
至于具体的鉴权是由AccessDecisionVoter
实现类来完成的。
AccessDecisionVoter
有三种返回结果:授权,弃权,拒绝
每个AccessDecisionManager
有一个AccessDecisionVoter
的集合,针对这个集合对权限的校验结果,
AccessDecisionManager
默认有三种决策方案。
AccessDecisionManager
有三个实现子类
AffirmativeBased
: 有授权通过的就直接授权通过
ConsensusBased
: 授权数大于拒绝数,则授权通过。至于授权数等于拒绝数,则根据配置决定。
UnanimousBased
:有拒绝就直接拒绝
好了,回到正题。
FilterSecurityInterceptor
的默认授权投票管理器(AccessDecisionManger
)方案是AffirmativeBased
,这也就是意味着,有一个授权通过就算通过了。授权失败直接抛AccessDeniedException
异常,然后交给ExceptionTranslationFilter
处理就行了。
6.RunAsManager 角色扮演
上面是在干啥呢?
1.RunAsManager去构建认证信息Authentication
当然FIlterSecurityInterceptor
默认的RunAsManager
实现类是NullRunAsManager
。这个类什么也不做。
但是如果要用的话,它有个实现类RunAsManagerImpl
,这个类就是将现有的权限ConfigAttribute
类中有以RUN_AS_
开头的权限。则在前面添加ROLE_
来作为一个角色权限。
2.当RunAsManager的鉴权信息为null时
说明没有需要扮演的角色。直接返回授权信息InterceptorStatusToken
3.当RunAsManager的鉴权信息不为null时
这个时候针对这个请求,由于权限得以临时提升,那么需要临时构建SecurityContext
来存储临时认证授权信息。 当然原来的SecurityContext
不能动,毕竟这个权限提升只是针对当前请求的。所以原始的SecurityContext
要保存起来,后面再重置回来。毕竟SecurityContext
是每个请求都要使用的。
7.beforeInvocation小结
这个就是用来进行url权限授权的,然后也可以针对指定请求进行权限提升。设计还是比较巧妙的。
其中AccessDecisionManager 授权决策管理器, AccessDecisionVoter 针对权限的检验器 两者的联合创造了更多权限校验方式。
可以瞅瞅权限校验器的子类
4. 核心方法2 finallyInvocation
这个其实就是为了将RunAsManager修改的SecurityContextHolder进行重置。
5. 核心方法3 afterInvocation
这个是干啥呢?就是在过滤器链执行完请求回来时,诶,我如果想对返回结果来一顿操作时,那么可以通过AfterInvocationManager来进行操作。
AfterInvocationManager
就跟AccessDecisionManager
很相似。
AfterInvocationManager
持有List<AfterInvocationProvider>
,通过AfterInvocationProvider
来进行修改返回结果。
6.小结
这个过滤器中,我了解了基于url匹配授权的基本过程。其中的三个重要组件,AccessDecisionManager,RunAsManager,AfterInvocationManager是值得深挖的。其中的设计思想和思路值得学习。
由于工作中暂时未使用到,且自身的学习计划比较紧张。所以这里针对许多可以扩展的地方并没有去扩展,由此产生的不好的阅读体验深感抱歉。