Spring?Security實現基于RBAC的權限表達式動態(tài)訪問控制的操作方法
昨天有個粉絲加了我,問我如何實現類似shiro的資源權限表達式的訪問控制。我以前有一個小框架用的就是shiro,權限控制就用了資源權限表達式,所以這個東西對我不陌生,但是在Spring Security中我并沒有使用過它,不過我認為Spring Security可以實現這一點。是的,我找到了實現它的方法。
資源權限表達式
說了這么多,我覺得應該解釋一下什么叫資源權限表達式。權限控制的核心就是清晰地表達出特定資源的某種操作,一個格式良好好的權限聲明可以清晰表達出用戶對該資源擁有的操作權限。
通常一個資源在系統(tǒng)中的標識是唯一的,比如User用來標識用戶,ORDER標識訂單。不管什么資源大都可以歸納出以下這幾種操作
在 shiro權限聲明通常對上面的這種資源操作關系用冒號分隔的方式進行表示。例如讀取用戶信息的操作表示為USER:READ
,甚至還可以更加細一些,用USER:READ:123
表示讀取ID為123
的用戶權限。
資源操作定義好了,再把它和角色關聯起來不就是基于RBAC的權限資源控制了嗎?就像下面這樣:
這樣資源和角色的關系可以進行CRUD操作進行動態(tài)綁定。
Spring Security中的實現
資源權限表達式動態(tài)權限控制在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); 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); 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')
來靜態(tài)綁定該接口的訪問權限表達式:
@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
動態(tài)化:
@PreAuthorize("hasPermission(#id,'USER','READ')") public Collection<String> postfilter(String id){ }
到此這篇關于Spring Security實現基于RBAC的權限表達式動態(tài)訪問控制的文章就介紹到這了,更多相關Spring Security實現RBAC權限表達式內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java面向對象程序設計:類的定義,靜態(tài)變量,成員變量,構造函數,封裝與私有,this概念與用法詳解
這篇文章主要介紹了Java面向對象類的定義,靜態(tài)變量,成員變量,構造函數,封裝與私有,this概念與用法,較為詳細的分析了Java類的定義,靜態(tài)變量,成員變量,構造函數,封裝,私有等相關原理、用法及操作注意事項,需要的朋友可以參考下2020-04-04詳解SpringMVC加載配置Properties文件的幾種方式
這篇文章主要介紹了詳解SpringMVC加載配置Properties文件的幾種方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-02-02Java如何讀寫Properties配置文件(Properties類)
這篇文章主要介紹了Java如何讀寫Properties配置文件(Properties類),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05