专栏名称: 码农小胖哥
技术公众号:码农小胖哥
目录
相关文章推荐
陕西省文化和旅游厅  ·  小众踏春好去处!和陕西的春天撞个满怀~ ·  23 小时前  
陕西省文化和旅游厅  ·  小众踏春好去处!和陕西的春天撞个满怀~ ·  23 小时前  
深圳晚报  ·  连休5天、7天、9天!多地发文 ·  昨天  
深圳晚报  ·  连休5天、7天、9天!多地发文 ·  昨天  
江西宣传  ·  开售!清明小长假出行买票有学问 ·  昨天  
江西宣传  ·  开售!清明小长假出行买票有学问 ·  昨天  
中国日报网  ·  中免集团全球品牌商大会在三亚举行 ·  3 天前  
常旅客专家  ·  国泰里程贬值!速囤航空次卡!假期出行超值! ·  3 天前  
51好读  ›  专栏  ›  码农小胖哥

Spring Security 实战干货:动态权限控制(下)实现

码农小胖哥  · 掘金  ·  · 2019-11-29 10:05

正文

阅读 10

Spring Security 实战干货:动态权限控制(下)实现

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 为分割线。举个例子







请到「今天看啥」查看全文