User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Spring   ›   SpringSecurity   ›   正文
SpringSecurity

SpringSecurity—权限管理的投票器与表决机制

2021-06-24 17:23:17
1052  0 0
参考目录 隐藏
1) 1.投票器
2) 1.1 RoleVoter
3) 1.2 RoleHierarchyVoter
4) 1.3 WebExpressionVoter
5) 1.4 其他
6) 2.表决机制
7) 3.在哪里配置

阅读完需:约 8 分钟

当用户想访问 Spring Security 中一个受保护的资源时,用户具备一些角色,该资源的访问也需要一些角色,在比对用户具备的角色和资源需要的角色时,就会用到投票器和表决机制。

当用户想要访问某一个资源时,投票器根据用户的角色投出赞成或者反对票,表决方式则根据投票器的结果进行表决。

在 Spring Security 中,默认提供了三种表决机制,当然,我们也可以不用系统提供的表决机制和投票器,而是完全自己来定义,这也是可以的。

重点介绍三种表决机制和默认的投票器。

1.投票器

先来看投票器。

在 Spring Security 中,投票器是由 AccessDecisionVoter 接口来规范的,我们来看下 AccessDecisionVoter 接口的实现:

可以看到,投票器的实现有好多种,我们可以选择其中一种或多种投票器,也可以自定义投票器,默认的投票器是 WebExpressionVoter。

我们来看 AccessDecisionVoter 的定义:

public interface AccessDecisionVoter<S> {
	int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED = -1;
	boolean supports(ConfigAttribute attribute);
	boolean supports(Class<?> clazz);
	int vote(Authentication authentication, S object,
			Collection<ConfigAttribute> attributes);
}

我稍微解释下:

  1. 首先一上来定义了三个常量,从常量名字中就可以看出每个常量的含义,1 表示赞成;0 表示弃权;-1 表示拒绝。
  2. 两个 supports 方法用来判断投票器是否支持当前请求。
  3. vote 则是具体的投票方法。在不同的实现类中实现。三个参数,authentication 表示当前登录主体;object 是一个 ilterInvocation,里边封装了当前请求;attributes 表示当前所访问的接口所需要的角色集合。

我们来分别看下几个投票器的实现。

1.1 RoleVoter

RoleVoter 主要用来判断当前请求是否具备该接口所需要的角色,我们来看下其 vote 方法:

public int vote(Authentication authentication, Object object,
		Collection<ConfigAttribute> attributes) {
	if (authentication == null) {
		return ACCESS_DENIED;
	}
	int result = ACCESS_ABSTAIN;
	Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
	for (ConfigAttribute attribute : attributes) {
		if (this.supports(attribute)) {
			result = ACCESS_DENIED;
			for (GrantedAuthority authority : authorities) {
				if (attribute.getAttribute().equals(authority.getAuthority())) {
					return ACCESS_GRANTED;
				}
			}
		}
	}
	return result;
}

这个方法的判断逻辑很简单,如果当前登录主体为 null,则直接返回 ACCESS_DENIED 表示拒绝访问;否则就从当前登录主体 authentication 中抽取出角色信息,然后和 attributes 进行对比,如果具备 attributes 中所需角色的任意一种,则返回 ACCESS_GRANTED 表示允许访问。例如 attributes 中的角色为 [a,b,c],当前用户具备 a,则允许访问,不需要三种角色同时具备。

另外还有一个需要注意的地方,就是 RoleVoter 的 supports 方法,我们来看下:

public class RoleVoter implements AccessDecisionVoter<Object> {
	private String rolePrefix = "ROLE_";
	public String getRolePrefix() {
		return rolePrefix;
	}
	public void setRolePrefix(String rolePrefix) {
		this.rolePrefix = rolePrefix;
	}
	public boolean supports(ConfigAttribute attribute) {
		if ((attribute.getAttribute() != null)
				&& attribute.getAttribute().startsWith(getRolePrefix())) {
			return true;
		}
		else {
			return false;
		}
	}
	public boolean supports(Class<?> clazz) {
		return true;
	}
}

可以看到,这里涉及到了一个 rolePrefix 前缀,这个前缀是 ROLE_,在 supports 方法中,只有主体角色前缀是 ROLE_,这个 supoorts 方法才会返回 true,这个投票器才会生效。

1.2 RoleHierarchyVoter

RoleHierarchyVoter 是 RoleVoter 的一个子类,在 RoleVoter 角色判断的基础上,引入了角色分层管理,也就是角色继承。

SpringBoot—SpringSecurity(角色继承问题) – Enamiĝu al vi (enmalvi.com)

RoleHierarchyVoter 类的 vote 方法和 RoleVoter 一致,唯一的区别在于 RoleHierarchyVoter 类重写了 extractAuthorities 方法。

@Override
Collection<? extends GrantedAuthority> extractAuthorities(
		Authentication authentication) {
	return roleHierarchy.getReachableGrantedAuthorities(authentication
			.getAuthorities());
}

角色分层之后,需要通过 getReachableGrantedAuthorities 方法获取实际具备的角色

1.3 WebExpressionVoter

这是一个基于表达式权限控制的投票器,松哥后面专门花点时间和小伙伴们聊一聊基于表达式的权限控制,这里我们先不做过多展开,简单看下它的 vote 方法:

public int vote(Authentication authentication, FilterInvocation fi,
		Collection<ConfigAttribute> attributes) {
	assert authentication != null;
	assert fi != null;
	assert attributes != null;
	WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
	if (weca == null) {
		return ACCESS_ABSTAIN;
	}
	EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
			fi);
	ctx = weca.postProcess(ctx, fi);
	return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
			: ACCESS_DENIED;
}

如果你熟练使用 SpEL 的话,这段代码应该说还是很好理解的

这里代码实际上就是根据传入的 attributes 属性构建 weca 对象,然后根据传入的 authentication 参数构建 ctx 对象,最后调用 evaluateAsBoolean 方法去判断权限是否匹配。

上面介绍这三个投票器是我们在实际开发中使用较多的三个。

1.4 其他

比较冷门的投票器

Jsr250Voter

处理 Jsr-250 权限注解的投票器,如 @PermitAll,@DenyAll 等。

AuthenticatedVoter

AuthenticatedVoter 用于判断 ConfigAttribute 上是否拥有 IS_AUTHENTICATED_FULLY、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY 三种角色。

IS_AUTHENTICATED_FULLY 表示当前认证用户必须是通过用户名/密码的方式认证的,通过 RememberMe 的方式认证无效。

IS_AUTHENTICATED_REMEMBERED 表示当前登录用户必须是通过 RememberMe 的方式完成认证的。

IS_AUTHENTICATED_ANONYMOUSLY 表示当前登录用户必须是匿名用户。

当项目引入 RememberMe 并且想区分不同的认证方式时,可以考虑这个投票器。

AbstractAclVoter

提供编写域对象 ACL 选项的帮助方法,没有绑定到任何特定的 ACL 系统。

PreInvocationAuthorizationAdviceVoter

使用 @PreFilter 和 @PreAuthorize 注解处理的权限,通过 PreInvocationAuthorizationAdvice 来授权。

当然,如果这些投票器不能满足需求,也可以自定义。

2.表决机制

一个请求不一定只有一个投票器,也可能有多个投票器,所以在投票器的基础上我们还需要表决机制。

表决相关的类主要是三个:

  • AffirmativeBased
  • ConsensusBased
  • UnanimousBased

他们的继承关系如上图。

三个决策器都会把项目中的所有投票器调用一遍,默认使用的决策器是 AffirmativeBased。

三个决策器的区别如下:

  • AffirmativeBased:有一个投票器同意了,就通过。
  • ConsensusBased:多数投票器同意就通过,平局的话,则看 allowIfEqualGrantedDeniedDecisions 参数的取值。
  • UnanimousBased 所有投票器都同意,请求才通过。

这里的具体判断逻辑比较简单,松哥就不贴源码了,感兴趣的小伙伴可以自己看看。

3.在哪里配置

当我们使用基于表达式的权限控制时,像下面这样:

http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")
        .antMatchers("/user/**").hasRole("user")
        .anyRequest().fullyAuthenticated()

那么默认的投票器和决策器是在 AbstractInterceptUrlConfigurer#createDefaultAccessDecisionManager 方法中配置的:

private AccessDecisionManager createDefaultAccessDecisionManager(H http) {
	AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));
	return postProcess(result);
}
List<AccessDecisionVoter<?>> getDecisionVoters(H http) {
	List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
	WebExpressionVoter expressionVoter = new WebExpressionVoter();
	expressionVoter.setExpressionHandler(getExpressionHandler(http));
	decisionVoters.add(expressionVoter);
	return decisionVoters;
}

这里就可以看到默认的决策器和投票器,并且决策器 AffirmativeBased 对象创建好之后,还调用 postProcess 方法注册到 Spring 容器中去了,结合松哥本系列前面的文章,大家知道,如果我们想要修改该对象就非常容易了:

http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")
        .antMatchers("/user/**").hasRole("user")
        .anyRequest().fullyAuthenticated()
        .withObjectPostProcessor(new ObjectPostProcessor<AffirmativeBased>() {
            @Override
            public <O extends AffirmativeBased> O postProcess(O object) {
                List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
                decisionVoters.add(new RoleHierarchyVoter(roleHierarchy()));
                AffirmativeBased affirmativeBased = new AffirmativeBased(decisionVoters);
                return (O) affirmativeBased;
            }
        })
        .and()
        .csrf()
        .disable();

这里只是给大家一个演示,正常来说我们是不需要这样修改的。当我们使用不同的权限配置方式时,会有自动配置对应的投票器和决策器。或者我们手动配置投票器和决策器,如果是系统配置好的,大部分情况下并不需要我们修改。

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

0 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 594146浏览

随机文章
WebSocket(四)—五子棋
5年前
Spring笔记14—整合MyBatis
5年前
SpringSecurity—防御会话固定攻击
4年前
Java—并发编程(七)JUC集合 – (6) ConcurrentSkipListSet
3年前
ElasticSearch—动态映射与静态映射(十)
5年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 594146 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付