Spring Security实现基于RBAC的权限表达式动态访问控制
昨天有个粉丝加了我,问我如何实现类似shiro的资源权限表达式的访问控制。我以前有一个小框架用的就是shiro,权限控制就用了资源权限表达式,所以这个东西对我不陌生,但是在Spring Security中我并没有使用过它,不过我认为Spring Security可以实现这一点。是的,我找到了实现它的方法。
资源权限表达式
说了这么多,我觉得应该解释一下什么叫资源权限表达式。权限控制的核心就是清晰地表达出特定资源的某种操作,一个格式良好好的权限声明可以清晰表达出用户对该资源拥有的操作权限。
通常一个资源在系统中的标识是唯一的,比如User用来标识用户,ORDER标识订单。不管什么资源大都可以归纳出以下这几种操作

在 shiro权限声明通常对上面的这种资源操作关系用冒号分隔的方式进行表示。例如读取用户信息的操作表示为USER:READ,甚至还可以更加细一些,用USER:READ:123表示读取ID为123的用户权限。
资源操作定义好了,再把它和角色关联起来不就是基于RBAC的权限资源控制了吗?就像下面这样:

这样资源和角色的关系可以进行CRUD操作进行动态绑定。
Spring Security中的实现
资源权限表达式动态权限控制在Spring Security也是可以实现的。首先开启方法级别的注解安全控制。
/**
* 开启方法安全注解
*
* @author felord.cn
*/
@EnableGlobalMethodSecurity(prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class MethodSecurityConfig {
}
MethodSecurityExpressionHandler
MethodSecurityExpressionHandler 提供了一个对方法进行安全访问的门面扩展。它的实现类DefaultMethodSecurityExpressionHandler更是提供了针对方法的一系列扩展接口,这里我总结了一下:

这里的PermissionEvaluator正好可以满足需要。
PermissionEvaluator
PermissionEvaluator 接口抽象了对一个用户是否有权限访问一个特定的领域对象的评估过程。
public interface PermissionEvaluator extends AopInfrastructureBean {
boolean hasPermission(Authentication authentication,
Object targetDomainObject, Object permission);
boolean hasPermission(Authentication authentication,
Serializable targetId, String targetType, Object permission);
}
这两个方法仅仅参数列表不同,这些参数的含义为:
authentication当前用户的认证信息,持有当前用户的角色权限。targetDomainObject用户想要访问的目标领域对象,例如上面的USER。permission这个当前方法设定的目标领域对象的权限,例如上面的READ。targetId这种是对上面targetDomainObject的具体化,比如ID为123的USER,我觉得还可以搞成租户什么的。targetType是为了配合targetId。
第一个方法是用来实现
USER:READ的;第二个方法是用来实现USER:READ:123的。
思路以及实现
targetDomainObject:permission不就是USER:READ的抽象吗?只要找出USER:READ对应的角色集合,和当前用户持有的角色进行比对,它们存在交集就证明用户有权限访问。借着这个思路胖哥实现了一个PermissionEvaluator:
/**
* 资源权限评估
*
* @author felord.cn
*/
public class ResourcePermissionEvaluator implements PermissionEvaluator {
private final BiFunction<String, String, Collection<? extends GrantedAuthority>> permissionFunction;
public ResourcePermissionEvaluator(BiFunction<String, String, Collection<? extends GrantedAuthority>> permissionFunction) {
this.permissionFunction = permissionFunction;
}
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
//查询方法标注对应的角色
Collection<? extends GrantedAuthority> resourceAuthorities = permissionFunction.apply((String) targetDomainObject, (String) permission);
// 用户对应的角色
Collection<? extends GrantedAuthority> userAuthorities = authentication.getAuthorities();
// 对比 true 就能访问 false 就不能访问
return userAuthorities.stream().anyMatch(resourceAuthorities::contains);
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
//todo
System.out.println("targetId = " + targetId);
return true;
}
}
第二个方法没有实现,因为两个差不多,第二个你可以想想具体的使用场景。
配置和使用
PermissionEvaluator 需要注入到Spring IoC,并且Spring IoC只能有一个该类型的Bean:
@Bean
PermissionEvaluator resourcePermissionEvaluator() {
return new ResourcePermissionEvaluator((targetDomainObject, permission) -> {
//TODO 这里形式其实可以不固定
String key = targetDomainObject + ":" + permission;
//TODO 查询 key 和 authority 的关联关系
// 模拟 permission 关联角色 根据key 去查 grantedAuthorities
Set<SimpleGrantedAuthority> grantedAuthorities = new HashSet<>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return "USER:READ".equals(key) ? grantedAuthorities : new HashSet<>();
});
}
接下来写个接口,用@PreAuthorize注解标记,然后直接用hasPermission('USER','READ')来静态绑定该接口的访问权限表达式:
@GetMapping("/postfilter")
@PreAuthorize("hasPermission('USER','READ')")
public Collection<String> postfilter(){
List<String> list = new ArrayList<>();
list.add("felord.cn");
list.add("码农小胖哥");
list.add("请关注一下");
return list;
}
然后定义一个用户:
@Bean
UserDetailsService users() {
UserDetails user = User.builder()
.username("felord")
.password("123456")
.passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode)
.roles("USER")
.authorities("ROLE_ADMIN","ROLE_USER")
.build();
return new InMemoryUserDetailsManager(user);
}
接下来肯定是正常能够访问接口的。当你改变了@PreAuthorize中表达式的值或者移除了用户的ROLE_ADMIN权限,再或者USER:READ关联到了其它角色等等,都会返回403。
留给你去测试的
你可以看看注解改成这样会是什么效果:
@PreAuthorize("hasPermission('1234','USER','READ')")
还有这个:
@PreAuthorize("hasPermission('USER','READ') or hasRole('ADMIN')")
或者让targetId动态化:
@PreAuthorize("hasPermission(#id,'USER','READ')")
public Collection<String> postfilter(String id){
}
关注公众号:Felordcn 获取更多资讯
Spring Security实现基于RBAC的权限表达式动态访问控制的更多相关文章
- Spring与Shiro整合 加载权限表达式
Spring与Shiro整合 加载权限表达式 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 如何加载权限表达式 我们在上章内容中画了一张图,里面有三个分项,用户 角色 权限: 那 ...
- 基于RBAC实现权限管理
基于RBAC实现权限管理 技术栈:SpringBoot.SpringMVC RBAC RBAC数据库表 主体 编号 账号 密码 001 admin 123456 资源 编号 资源名称 访问路径 001 ...
- 基于RBAC的权限控制浅析(结合Spring Security)
嗯,昨天面试让讲我的项目,让我讲讲项目里权限控制那一块的,讲的很烂.所以整理一下. 按照面试官的提问流程来讲: 一.RBAC是个啥东西了? RBAC(Role-Based Access Control ...
- 基于Spring Security 的JSaaS应用的权限管理
1. 概述 权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源.资源包括访问的页面,访问的数据等,这在传统的应用系统中比较常见.本文介绍的则是基于Saas系统 ...
- spring security实现简单的url权限拦截
在一个系统中,权限的拦截是很常见的事情,通常情况下我们都是基于url进行拦截.那么在spring security中应该怎么配置呢. 大致步骤如下: 1.用户登录成功后我们需要拿到用户所拥有的权限,并 ...
- Spring Security构建Rest服务-1401-权限表达式
Spring Security 的权限表达式 用法,在自定义的BrowserSecurityConfig extends WebSecurityConfigurerAdapter 配置文件里,每一个a ...
- springboot整合security实现基于url的权限控制
权限控制基本上是任何一个web项目都要有的,为此spring为我们提供security模块来实现权限控制,网上找了很多资料,但是提供的demo代码都不能完全满足我的需求,因此自己整理了一版. 在上代码 ...
- spring security采用基于简单加密 token 的方法实现的remember me功能
记住我功能,相信大家在一些网站已经用过,一些安全要求不高的都可以使用这个功能,方便快捷. spring security针对该功能有两种实现方式,一种是简单的使用加密来保证基于 cookie 的 to ...
- ThinkPHP框架下基于RBAC的权限控制模式详解
这几天因为要做一个项目,需要可以对Web应用中通用功能进行封装,其中一个很重要的涉及到了对用户.角色和权限部分的灵活管理.所以基于TP框架自己封装了一个对操作权限和菜单权限进行灵活配置的可控制模式. ...
随机推荐
- PCIe Tandem PROM 方法
PCIe Tandem PROM 方法 什么是Tandem PROM? 简单总结:市面多数的FPGA都是SRAM型,需要在上电时从外部存储器件完成代码的加载,对于具有PCIe功能的SRAM FPGA而 ...
- 【Python 第1课】安装
在Windows系统上安装Python的方法还算简单,比平常装个软件稍稍麻烦一点.进入Python的官方下载页面Python.org/download,你会看到一堆下载链接.我们就选"Pyt ...
- B+树叶子节点数据如何存储,以及如何查找某一条数据
MySQL索引背后的数据结构及算法原理 https://www.kancloud.cn/kancloud/theory-of-mysql-index 非常好 根据一条sql 如何查看索引结构等信息 ...
- switch 是否能作用在byte 上,是否能作用在long 上,是否能作用在String上?
答:在Java 5以前,switch(expr)中,expr只能是byte.short.char.int.从Java 5开始,Java中引入了枚举类型,expr也可以是enum类型,从Java 7开始 ...
- gradle构建scala
1. 在目录下创建build.gradle文件,内容为: apply plugin: 'idea' apply plugin: 'scala' repositories { mavenLocal() ...
- 2. 使用Github
2. 使用Github 2.1 目的 借助github托管项目代码 2.2 基本概念 仓库(Repository) 仓库用来存放项目代码,每个项目对应一个仓库,多个开源项目则有多个仓库 收藏(Star ...
- C++ | 虚函数表内存布局
虚表指针 虚函数有个特点.存在虚函数的类会在类的数据成员中生成一个虚函数指针 vfptr,而vfptr 指向了一张表(简称,虚表).正是由于虚函数的这个特性,C++的多态才有了发生的可能. 其中虚函数 ...
- C++ | 简单工厂模式 | 复数计算器
简单工厂模式最直观的一个应用便是实现一个计算器的程序. 比如,公司让你给计算器添加一个幂运算的功能,你只需要设计一个幂运算的类,并实现幂运算的逻辑,然后让该类继承自运算类即可. 简单工厂模式: 简单工 ...
- POJ 2236:Wireless Network
描述 n台电脑,如果两台电脑间的距离的d范围内,则两台电脑能够连通. 如果AB连通,BC连通,则认为AC连通. 已知电脑台数N,最大距离d,以及每个电脑的坐标.有如下两种操作: O i 表示修复编号为 ...
- GoLang数组切片
1. 数组1.1 如何定义数组同java数组一样,数组是一组内存连续且类型相同的数据组成 //不初始化初始值默认为0 var arr1 = [5]int{} var arr2 = [5]int{1,2 ...