当前位置: 首页 > 后端技术 > Java

SpringSecurity实现基于RBAC的权限表达式动态访问控制

时间:2023-04-01 20:51:44 Java

昨天有个粉丝加我,问我如何实现类似shiro的资源权限表达式访问控制。我以前有一个小框架,用的是shiro,用的是资源权限表达式来进行权限控制,所以这个东西我比较熟悉,但是在SpringSecurity里面没用过,不过我觉得SpringSecurity可以实现这个。是的,我找到了一种方法。资源权限表达式说了这么多,我觉得应该解释一下什么是资源权限表达式。权限控制的核心是明确表达对特定资源的某种操作。一个格式良好的权限声明可以清楚地表达用户对资源的操作权限。通常一个资源在系统中是唯一标识的,比如User用来标识一个用户,ORDER用来标识一个订单。无论是什么资源,都可以归纳出以下几类操作。在shiro权限声明中,上面的资源操作关系通常用冒号分隔的方式表示。比如读取用户信息的操作,表示为USER:READ,甚至可以更细化,用USER:READ:123表示允许读取ID为123的用户。资源操作定义后,关联一个角色不就是基于RBAC的权限资源控制吗?像下面这样:这样就可以通过CRUD操作动态绑定资源和角色之间的关系。SpringSecurity中资源权限表达式动态权限控制的实现,在SpringSecurity中也是可以实现的。首先开启方法级别的注解安全控制。/***启用方法安全注解**@authorfelord.cn*/@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true,jsr250Enabled=true)publicclassMethodSecurityConfig{}MethodSecurityExpressionHandlerMethodSecurityExpressionHandler为安全访问方法提供了一个外观。它的实现类DefaultMethodSecurityExpressionHandler为方法提供了一系列的扩展接口。这里我总结一下:这里的PermissionEvaluator正好可以满足需求。PermissionEvaluatorPermissionEvaluator接口抽象了评估用户是否有权访问特定域对象的过程。publicinterfacePermissionEvaluatorextendsAopInfrastructureBean{booleanhasPermission(Authenticationauthentication,ObjecttargetDomainObject,Objectpermission);booleanhasPermission(Authenticationauthentication,SerializabletargetId,StringtargetType,Objectpermission);这两个方法参数的含义只是不同,:authentication当前用户的认证信息,持有当前用户的角色权限。targetDomainObject用户想要访问的目标领域对象,比如上面的USER。permission当前方法设置的目标域对象的权限,如上面的READ。targetId是上面targetDomainObject的具体化,比如ID为123的USER,我觉得可以做成tenant什么的。targetType是为了匹配targetId。第一种方法用于实现USER:READ;第二种方法是用来实现USER:READ:123targetDomainObject:permission的思想和实现不就是USER:READ的抽象吗?只要找出USER:READ对应的角色集合,与当前用户持有的角色进行比较,相交就证明该用户有权限访问。带着这个想法,胖哥实现了一个PermissionEvaluator:/***资源权限评估**@authorfelord.cn*/publicclassResourcePermissionEvaluatorimplementsPermissionEvaluator{privatefinalBiFunction>permissionFunction;publicResourcePermissionEvaluator(BiFunction>permissionFunction){this.permissionFunction=permissionFunction;}@OverridepublicbooleanhasPermission(Authenticationauthentication,ObjecttargetDomainObject,Objectpermission){//对应的查询方法CollectionresourceAuthorities=permissionFunction.apply((String)targetDomainObject,(String)permission);//用户对应的角色CollectionuserAuthorities=authentication.getAuthorities();//比较true才能访问False不能访问returnuserAuthorities.stream().anyMatch(resourceAuthorities::包含);}@OverridepublicbooleanhasPermission(Authenticationauthentication,SerializabletargetId,StringtargetType,Objectpermission){//todoSystem.out.println("targetId="+targetId);返回真;}}第二种第一种方法没有实现,因为两者差不多。第二种你可以想想具体的使用场景。配置和使用PermissionEvaluator需要注入到SpringIoC中,SpringIoC只能有一个这种类型的Bean:@BeanPermissionEvaluatorresourcePermissionEvaluator(){returnnewResourcePermissionEvaluator((targetDomainObject,permission)->{//TODO这里的形式可以实际上是unfixedStringkey=targetDomainObject+":"+permission;//TODO查询key和权限的关系//模拟权限关联角色查看grantedAuthoritiesSetgrantedAuthorities=newHashSet<>();grantedAuthorities.add(newSimpleGrantedAuthority("ROLE_ADMIN"));返回"USER:READ".equals(key)?grantedAuthorities:newHashSet<>();});接下来写一个接口,打上@PreAuthorize注解,使用hasPermission('USER','READ')静态绑定接口的访问权限表达式:@GetMapping("/postfilter")@PreAuthorize("hasPermission('USER','READ')")publicCollectionpostfilter(){List<;String>list=newArrayList<>();list.add("felord.cn");list.add("码农小胖哥");list.add("请注意");返回列表;}然后定义一个用户:@BeanUserDetailsS??erviceusers(){UserDetailsuser=User.builder().username("felord").password("123456").passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode).roles(“USER”).authorities(“ROLE_ADMIN”,“ROLE_USER”).build();返回新的InMemoryUserDetailsManager(用户);那么当你改变@PreAuthorize中的表达式的值或者移动用户的ROLE_ADMIN权限,或者USER:READ关联其他角色等时,你必须能够正常访问接口等,都会返回403。为了测试,您可以看到将注释更改为此的效果是:@PreAuthorize("hasPermission('1234','USER','READ')")和这个:@PreAuthorize("hasPermission('USER','READ')或hasRole('ADMIN')")或使targetId动态:@PreAuthorize("hasPermission(#id,'USER','READ')")publicCollectionpostfilter(Stringid){}关注公众号:Felordcn获取更多资讯个人博客:https://felord.cn