1. 前言
Spring Security 实战干货:内置 Filter 全解析
中提到的第
32
个
Filter
不知道你是否有印象。它决定了访问特定路径应该具备的权限,访问的用户的角色,权限是什么?访问的路径需要什么样的角色和权限? 它就是
FilterSecurityInterceptor
,正是我们需要的那个轮子。
2.FilterSecurityInterceptor
过滤器排行榜第 32 位!肩负对 http 接口权限认证的重要职责。我们来看它的过滤逻辑:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
复制代码
初始化了一个
FilterInvocation
然后被
invoke
方法处理:
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 {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
复制代码
每一次请求被
Filter
过滤都会被打上标记
FILTER_APPLIED
,没有被打上标记的 走了父类的
beforeInvocation
方法然后再进入过滤器链,看上去是走了一个前置的处理。那么前置处理了什么呢?
首先会通过
this.obtainSecurityMetadataSource().getAttributes(Object object)
拿受保护对象(就是当前请求的 URI)所有的映射角色(
ConfigAttribute
直接理解为角色的进一步抽象) 。然后使用访问决策管理器
AccessDecisionManager
进行投票决策来确定是否放行。 我们来看一下这两个接口。
安全拦截器和“安全对象”模型参考:
3. 元数据加载器
元数据加载器
FilterInvocationSecurityMetadataSource
是
FilterSecurityInterceptor
的属性,UML 图如下:
FilterInvocationSecurityMetadataSource
是一个标记接口,其抽象方法继承自
SecurityMetadataSource``AopInfrastructureBean
。它的作用是来获取我们
上一篇
文章所描述的
资源角色元数据
。
- Collection getAttributes(Object object) 根据提供的受保护对象的信息,其实就是 URI,获取该 URI 配置的所有角色
- Collection getAllConfigAttributes() 这个就是获取全部角色
-
boolean supports(Class<?> clazz)
对特定的安全对象是否提供
ConfigAttribute
支持
3.1 自定义实现思路
所有的思路仅供参考,实际以你的业务为准!
Collection<ConfigAttribute> getAttributes(Object object)
方法的实现:肯定是获取请求中的
URI
来和 所有的 资源配置中的
Ant Pattern
进行匹配以获取对应的资源配置, 这里需要将资源查询接口查询的资源配置封装为
AntPathRequestMatcher
以方便进行
Ant Match
。
这里需要特别提一下如果你使用
Restful
风格,这里
增删改查
将非常方便你来对资源的管控。参考的实现:
@Bean
public RequestMatcherCreator requestMatcherCreator() {
return metaResources -> metaResources.stream()
.map(metaResource -> new AntPathRequestMatcher(metaResource.getPattern(), metaResource.getMethod()))
.collect(Collectors.toSet());
}
复制代码
HttpRequest
匹配到对应的资源配置后就能根据资源配置去取对应的角色集合。这些角色将交给访问决策管理器
AccessDecisionManager
进行投票表决以决定是否放行。
4. 决策管理器
决策管理器
AccessDecisionManager
用来投票决定是否放行请求。
public interface AccessDecisionManager {
// 决策 主要通过其持有的 AccessDecisionVoter 来进行投票决策
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;
// 以确定AccessDecisionManager是否可以处理传递的ConfigAttribute
boolean supports(ConfigAttribute attribute);
//以确保配置的AccessDecisionManager支持安全拦截器将呈现的安全 object 类型。
boolean supports(Class<?> clazz);
}
复制代码
AccessDecisionManager
有三个默认实现:
- AffirmativeBased 基于肯定的决策器。 用户持有一个同意访问的角色就能通过。
- ConsensusBased 基于共识的决策器。 用户持有同意的角色数量多于禁止的角色数。
- UnanimousBased 基于一致的决策器。 用户持有的所有角色都同意访问才能放行。
投票决策模型参考:
4.1 自定义决策管理器
动态控制权限就需要我们实现自己的访问决策器。我们上面说了默认有三个实现,这里我选择基于肯定的决策器
AffirmativeBased
,只要用户持有一个持有一个角色包含想要访问的资源就能访问该资源。接下来就是投票器
AccessDecisionVoter
的定义了,其实我们可以选择内置的
5. 决策投票器
决策投票器
AccessDecisionVoter
将安全配置属性
ConfigAttribute
以特定的逻辑进行解析并基于特定的策略来进行投票,投赞成票时总票数
+1
,反对票总票数
-1
,弃权时总票数
+0
, 然后由
AccessDecisionManager
根据具体的计票策略来决定是否放行。
5.1 角色投票器
Spring Security
提供的最常用的投票器是角色投票器
RoleVoter
,它将安全配置属性
ConfigAttribute
视为简单的角色名称,并在用户被分配了该角色时授予访问权限。
如果任何
ConfigAttribute
以前缀
ROLE_
开头,它将投票。如果有一个
GrantedAuthority
返回一个字符串(通过
getAuthority()
方法)正好等于一个或多个从前缀
ROLE_
开始的
ConfigAttributes
,它将投票授予访问权限。如果没有任何以
ROLE_
开头的
ConfigAttributes
匹配,则
RoleVoter
将投票拒绝访问。如果没有
ConfigAttribute
以 ROLE_为前缀,将弃权。
这正是我们想要的投票器。
5.2 角色分层投票器
通常要求应用程序中的特定角色应自动“包含”其他角色。例如,在具有
ROLE_ADMIN
和
ROLE_USER
角色概念的应用中,您可能希望管理员能够执行普通用户可以执行的所有操作。你不得不进行各种复杂的逻辑嵌套来满足这一需求。现在幸好有了
RoleHierarchyVoter
可以帮你减少这种负担。
它由上面的
RoleVoter
派生,通过配置了一个
RoleHierarchy
就可以实现
ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST
这种层次包含结构,
左边的一定能访问右边可以访问的资源
。具体的配置规则为:角色从左到右、从高到低以
>
相连(注意两个空格),以换行符
\n
为分割线。举个例子