Spring Security实现基于RBAC的权限表达式动态访问控制的操作方法

作者:码农小胖哥 时间:2023-11-29 16:03:25 

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

资源权限表达式

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

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

Spring Security实现基于RBAC的权限表达式动态访问控制的操作方法

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

资源操作定义好了,再把它和角色关联起来不就是基于RBAC的权限资源控制了吗?就像下面这样:

Spring Security实现基于RBAC的权限表达式动态访问控制的操作方法

这样资源和角色的关系可以进行CRUD操作进行动态绑定。

Spring Security中的实现

资源权限表达式动态权限控制在Spring Security也是可以实现的。首先开启方法级别的注解安全控制。

/**
* 开启方法安全注解
*
* @author felord.cn
*/
@EnableGlobalMethodSecurity(prePostEnabled = true,
       securedEnabled = true,
       jsr250Enabled = true)
public class MethodSecurityConfig {

}

MethodSecurityExpressionHandler

MethodSecurityExpressionHandler 提供了一个对方法进行安全访问的门面扩展。它的实现类DefaultMethodSecurityExpressionHandler更是提供了针对方法的一系列扩展接口,这里我总结了一下:

Spring Security实现基于RBAC的权限表达式动态访问控制的操作方法

这里的PermissionEvaluator正好可以满足需要。

PermissionEvaluator

PermissionEvaluator 接口抽象了对一个用户是否有权限访问一个特定的领域对象的评估过程。

public interface PermissionEvaluator extends AopInfrastructureBean {
boolean hasPermission(Authentication authentication,
                         Object targetDomainObject, Object permission);
                         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);
   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){

}

来源:https://www.cnblogs.com/felordcn/p/16167532.html

标签:Spring,Security,RBAC,权限,控制
0
投稿

猜你喜欢

  • java通过PDF模板填写PDF表单

    2022-05-28 16:37:48
  • Spring自动配置之condition条件判断下篇

    2023-10-15 10:04:59
  • springboot实现返回文件流

    2023-04-04 22:19:22
  • C语言一级指针二级指针和三级指针区别及使用详解

    2022-10-21 03:29:34
  • 微服务通过Feign调用进行密码安全认证操作

    2023-07-30 02:43:38
  • SpringBoot接口如何统一异常处理

    2023-08-10 15:06:20
  • C#实现的MD5加密功能与用法示例

    2023-06-11 09:08:21
  • C#并行编程之信号量

    2023-10-08 19:26:54
  • idea mybatis配置log4j打印sql语句的示例

    2023-11-25 10:32:39
  • AndroidStudio Gradle基于友盟的多渠道打包方法

    2022-02-24 22:04:43
  • 深入解析Java中的Classloader的运行机制

    2023-07-16 11:47:59
  • Java实现斗地主最简代码实例

    2023-07-11 18:40:02
  • Android Handler,Message,MessageQueue,Loper源码解析详解

    2022-04-17 17:34:57
  • Kotlin Suspend挂起函数的使用详解

    2022-11-01 12:52:56
  • logcat命令使用方法和查看android系统日志缓冲区内容的方法

    2022-06-09 10:42:36
  • Android convinientbanner顶部广告轮播控件使用详解

    2021-08-20 13:09:09
  • 微信小程序获取手机号,后端JAVA解密流程代码

    2023-11-29 07:57:26
  • 详解C#如何利用爬虫技术实现快捷租房

    2021-11-02 21:49:38
  • Android开发gradle拉取依赖的加速配置

    2023-05-31 03:16:08
  • Spring Bean后处理器详细介绍

    2021-06-27 07:29:06
  • asp之家 软件编程 m.aspxhome.com