昨天有个粉丝加了我,问我如何实现类似shiro的资源权限表达式的访问控制。我以前有一个小框架用的就是shiro,权限控制就用了资源权限表达式,所以这个东西对我不陌生,但是在Spring Security中我并没有使用过它,不过我认为Spring Security可以实现这一点。是的,我找到了实现它的方法。

资源权限表达式

说了这么多,我觉得应该解释一下什么叫资源权限表达式。权限控制的核心就是清晰地表达出特定资源的某种操作,一个格式良好好的权限声明可以清晰表达出用户对该资源拥有的操作权限。

通常一个资源在系统中的标识是唯一的,比如User用来标识用户,ORDER标识订单。不管什么资源大都可以归纳出以下这几种操作

在 shiro权限声明通常对上面的这种资源操作关系用冒号分隔的方式进行表示。例如读取用户信息的操作表示为USER:READ,甚至还可以更加细一些,用USER:READ:123表示读取ID123的用户权限。

资源操作定义好了,再把它和角色关联起来不就是基于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为123USER,我觉得还可以搞成租户什么的。
  • 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 获取更多资讯

个人博客:https://felord.cn

Spring Security实现基于RBAC的权限表达式动态访问控制的更多相关文章

  1. Spring与Shiro整合 加载权限表达式

    Spring与Shiro整合 加载权限表达式 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 如何加载权限表达式  我们在上章内容中画了一张图,里面有三个分项,用户 角色 权限: 那 ...

  2. 基于RBAC实现权限管理

    基于RBAC实现权限管理 技术栈:SpringBoot.SpringMVC RBAC RBAC数据库表 主体 编号 账号 密码 001 admin 123456 资源 编号 资源名称 访问路径 001 ...

  3. 基于RBAC的权限控制浅析(结合Spring Security)

    嗯,昨天面试让讲我的项目,让我讲讲项目里权限控制那一块的,讲的很烂.所以整理一下. 按照面试官的提问流程来讲: 一.RBAC是个啥东西了? RBAC(Role-Based Access Control ...

  4. 基于Spring Security 的JSaaS应用的权限管理

    1. 概述 权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源.资源包括访问的页面,访问的数据等,这在传统的应用系统中比较常见.本文介绍的则是基于Saas系统 ...

  5. spring security实现简单的url权限拦截

    在一个系统中,权限的拦截是很常见的事情,通常情况下我们都是基于url进行拦截.那么在spring security中应该怎么配置呢. 大致步骤如下: 1.用户登录成功后我们需要拿到用户所拥有的权限,并 ...

  6. Spring Security构建Rest服务-1401-权限表达式

    Spring Security 的权限表达式 用法,在自定义的BrowserSecurityConfig extends WebSecurityConfigurerAdapter 配置文件里,每一个a ...

  7. springboot整合security实现基于url的权限控制

    权限控制基本上是任何一个web项目都要有的,为此spring为我们提供security模块来实现权限控制,网上找了很多资料,但是提供的demo代码都不能完全满足我的需求,因此自己整理了一版. 在上代码 ...

  8. spring security采用基于简单加密 token 的方法实现的remember me功能

    记住我功能,相信大家在一些网站已经用过,一些安全要求不高的都可以使用这个功能,方便快捷. spring security针对该功能有两种实现方式,一种是简单的使用加密来保证基于 cookie 的 to ...

  9. ThinkPHP框架下基于RBAC的权限控制模式详解

    这几天因为要做一个项目,需要可以对Web应用中通用功能进行封装,其中一个很重要的涉及到了对用户.角色和权限部分的灵活管理.所以基于TP框架自己封装了一个对操作权限和菜单权限进行灵活配置的可控制模式. ...

随机推荐

  1. IC设计基础

    一 前言 这一周连续两场线下面试,紧接着又是微信视频面试,从连续三天的面试中,收获颇丰! 存在的问题: 一是对项目细节模糊: 二是IC基础知识薄弱: 具体表现是,在面试过程中,如被问到DDR3和千兆以 ...

  2. 计算机网络:套接字(Socket)| Python socket实现服务器端与客户端通信,使用TCP socket阿里云ECS服务器与本机通信

    所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象.一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制.从所处的地位来讲,套接字上联应 ...

  3. centos配置ssh服务并简单测试

    最近在做计算机集群方面的东西,简单弄了一下ssh服务. 首先把前提情况介绍一下: 1.我是用的虚拟机先模拟的,也不是没有真机,就是跑来跑去麻烦. 2.装了三个相同配置的centos虚拟机,详细参数就不 ...

  4. BUU findkey

    定位关键函数 跟入flag找到问题位置 两行一样的代码,nop掉第二行,按p生成函数 代码审计 int __userpurge sub_4018C4@<eax>(int a1@<eb ...

  5. Java编程:Lock

    在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方 ...

  6. B树、B+树、B*树三者的对比详解

    转载至:https://www.2cto.com/database/201805/745822.html 对比 B+树是B树的变体,B*树又是B+树的变体,是一脉相承法治国拉的,不断解决新一阶段的问题 ...

  7. Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?

    不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复:如果没有配 置 namespace,那么 id 不能重复: 原因就是 namespace+id 是作为 Map<S ...

  8. 如何在网上找MySQL数据库的JDBC驱动jar包?

    当我们在开发程序,涉及数据库时,总是需要用到相应的jar包,这不小编就给大家介绍一下如何下载相应的jar包 方法/步骤   1 在百度搜索栏上搜索MySQL 2 选择Downloads 3 选择 Co ...

  9. 哪一个List实现了最快插入?

    LinkedList和ArrayList是另个不同变量列表的实现.ArrayList的优势在于动态的增长数组,非常适合初始时总长度未知的情况下使用.LinkedList的优势在于在中间位置插入和删除操 ...

  10. 转:红黑树和AVL树(平衡二叉树)区别

    本文转载至链接:https://blog.csdn.net/u010899985/article/details/80981053 一.AVL树(平衡二叉树) (1)简介 AVL树是带有平衡条件的二叉 ...