Spring?Security實(shí)現(xiàn)基于RBAC的權(quán)限表達(dá)式動(dòng)態(tài)訪問(wèn)控制的操作方法
昨天有個(gè)粉絲加了我,問(wèn)我如何實(shí)現(xiàn)類(lèi)似shiro的資源權(quán)限表達(dá)式的訪問(wèn)控制。我以前有一個(gè)小框架用的就是shiro,權(quán)限控制就用了資源權(quán)限表達(dá)式,所以這個(gè)東西對(duì)我不陌生,但是在Spring Security中我并沒(méi)有使用過(guò)它,不過(guò)我認(rèn)為Spring Security可以實(shí)現(xiàn)這一點(diǎn)。是的,我找到了實(shí)現(xiàn)它的方法。
資源權(quán)限表達(dá)式
說(shuō)了這么多,我覺(jué)得應(yīng)該解釋一下什么叫資源權(quán)限表達(dá)式。權(quán)限控制的核心就是清晰地表達(dá)出特定資源的某種操作,一個(gè)格式良好好的權(quán)限聲明可以清晰表達(dá)出用戶對(duì)該資源擁有的操作權(quán)限。
通常一個(gè)資源在系統(tǒng)中的標(biāo)識(shí)是唯一的,比如User用來(lái)標(biāo)識(shí)用戶,ORDER標(biāo)識(shí)訂單。不管什么資源大都可以歸納出以下這幾種操作
在 shiro權(quán)限聲明通常對(duì)上面的這種資源操作關(guān)系用冒號(hào)分隔的方式進(jìn)行表示。例如讀取用戶信息的操作表示為USER:READ
,甚至還可以更加細(xì)一些,用USER:READ:123
表示讀取ID為123
的用戶權(quán)限。
資源操作定義好了,再把它和角色關(guān)聯(lián)起來(lái)不就是基于RBAC的權(quán)限資源控制了嗎?就像下面這樣:
這樣資源和角色的關(guān)系可以進(jìn)行CRUD操作進(jìn)行動(dòng)態(tài)綁定。
Spring Security中的實(shí)現(xiàn)
資源權(quán)限表達(dá)式動(dòng)態(tài)權(quán)限控制在Spring Security也是可以實(shí)現(xiàn)的。首先開(kāi)啟方法級(jí)別的注解安全控制。
/** * 開(kāi)啟方法安全注解 * * @author felord.cn */ @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) public class MethodSecurityConfig { }
MethodSecurityExpressionHandler
MethodSecurityExpressionHandler
提供了一個(gè)對(duì)方法進(jìn)行安全訪問(wèn)的門(mén)面擴(kuò)展。它的實(shí)現(xiàn)類(lèi)DefaultMethodSecurityExpressionHandler
更是提供了針對(duì)方法的一系列擴(kuò)展接口,這里我總結(jié)了一下:
這里的PermissionEvaluator
正好可以滿足需要。
PermissionEvaluator
PermissionEvaluator
接口抽象了對(duì)一個(gè)用戶是否有權(quán)限訪問(wèn)一個(gè)特定的領(lǐng)域?qū)ο蟮脑u(píng)估過(guò)程。
public interface PermissionEvaluator extends AopInfrastructureBean { boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission); Serializable targetId, String targetType, Object permission); }
這兩個(gè)方法僅僅參數(shù)列表不同,這些參數(shù)的含義為:
authentication
當(dāng)前用戶的認(rèn)證信息,持有當(dāng)前用戶的角色權(quán)限。targetDomainObject
用戶想要訪問(wèn)的目標(biāo)領(lǐng)域?qū)ο?,例如上面?code>USER。permission
這個(gè)當(dāng)前方法設(shè)定的目標(biāo)領(lǐng)域?qū)ο蟮臋?quán)限,例如上面的READ
。targetId
這種是對(duì)上面targetDomainObject
的具體化,比如ID為123
的USER
,我覺(jué)得還可以搞成租戶什么的。targetType
是為了配合targetId
。
第一個(gè)方法是用來(lái)實(shí)現(xiàn)USER:READ
的;第二個(gè)方法是用來(lái)實(shí)現(xiàn)USER:READ:123
的。
思路以及實(shí)現(xiàn)
targetDomainObject:permission
不就是USER:READ
的抽象嗎?只要找出USER:READ
對(duì)應(yīng)的角色集合,和當(dāng)前用戶持有的角色進(jìn)行比對(duì),它們存在交集就證明用戶有權(quán)限訪問(wèn)。借著這個(gè)思路胖哥實(shí)現(xiàn)了一個(gè)PermissionEvaluator
:
/** * 資源權(quán)限評(píng)估 * * @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) { //查詢(xún)方法標(biāo)注對(duì)應(yīng)的角色 Collection<? extends GrantedAuthority> resourceAuthorities = permissionFunction.apply((String) targetDomainObject, (String) permission); // 用戶對(duì)應(yīng)的角色 Collection<? extends GrantedAuthority> userAuthorities = authentication.getAuthorities(); // 對(duì)比 true 就能訪問(wèn) false 就不能訪問(wèn) 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; }
第二個(gè)方法沒(méi)有實(shí)現(xiàn),因?yàn)閮蓚€(gè)差不多,第二個(gè)你可以想想具體的使用場(chǎng)景。
配置和使用
PermissionEvaluator
需要注入到Spring IoC,并且Spring IoC只能有一個(gè)該類(lèi)型的Bean:
@Bean PermissionEvaluator resourcePermissionEvaluator() { return new ResourcePermissionEvaluator((targetDomainObject, permission) -> { //TODO 這里形式其實(shí)可以不固定 String key = targetDomainObject + ":" + permission; //TODO 查詢(xún) key 和 authority 的關(guān)聯(lián)關(guān)系 // 模擬 permission 關(guān)聯(lián)角色 根據(jù)key 去查 grantedAuthorities Set<SimpleGrantedAuthority> grantedAuthorities = new HashSet<>(); grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); return "USER:READ".equals(key) ? grantedAuthorities : new HashSet<>(); }); }
接下來(lái)寫(xiě)個(gè)接口,用@PreAuthorize
注解標(biāo)記,然后直接用hasPermission('USER','READ')
來(lái)靜態(tài)綁定該接口的訪問(wèn)權(quán)限表達(dá)式:
@GetMapping("/postfilter") @PreAuthorize("hasPermission('USER','READ')") public Collection<String> postfilter(){ List<String> list = new ArrayList<>(); list.add("felord.cn"); list.add("碼農(nóng)小胖哥"); list.add("請(qǐng)關(guān)注一下"); return list; }
然后定義一個(gè)用戶:
@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); }
接下來(lái)肯定是正常能夠訪問(wèn)接口的。當(dāng)你改變了@PreAuthorize
中表達(dá)式的值或者移除了用戶的ROLE_ADMIN
權(quán)限,再或者USER:READ
關(guān)聯(lián)到了其它角色等等,都會(huì)返回403
。
留給你去測(cè)試的
你可以看看注解改成這樣會(huì)是什么效果:
@PreAuthorize("hasPermission('1234','USER','READ')")
還有這個(gè):
@PreAuthorize("hasPermission('USER','READ') or hasRole('ADMIN')")
或者讓targetId
動(dòng)態(tài)化:
@PreAuthorize("hasPermission(#id,'USER','READ')") public Collection<String> postfilter(String id){ }
到此這篇關(guān)于Spring Security實(shí)現(xiàn)基于RBAC的權(quán)限表達(dá)式動(dòng)態(tài)訪問(wèn)控制的文章就介紹到這了,更多相關(guān)Spring Security實(shí)現(xiàn)RBAC權(quán)限表達(dá)式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java面向?qū)ο蟪绦蛟O(shè)計(jì):類(lèi)的定義,靜態(tài)變量,成員變量,構(gòu)造函數(shù),封裝與私有,this概念與用法詳解
這篇文章主要介紹了Java面向?qū)ο箢?lèi)的定義,靜態(tài)變量,成員變量,構(gòu)造函數(shù),封裝與私有,this概念與用法,較為詳細(xì)的分析了Java類(lèi)的定義,靜態(tài)變量,成員變量,構(gòu)造函數(shù),封裝,私有等相關(guān)原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04詳解SpringMVC加載配置Properties文件的幾種方式
這篇文章主要介紹了詳解SpringMVC加載配置Properties文件的幾種方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02Java語(yǔ)言通過(guò)三種方法實(shí)現(xiàn)隊(duì)列的示例代碼
這篇文章主要介紹了Java語(yǔ)言通過(guò)三種方法來(lái)實(shí)現(xiàn)隊(duì)列的實(shí)例代碼,數(shù)組模擬隊(duì)列,通過(guò)對(duì)定義的了解,發(fā)現(xiàn)隊(duì)列很像我們的數(shù)組,下面我們通過(guò)實(shí)踐給大家詳細(xì)介紹,需要的朋友可以參考下2022-02-02Java如何讀寫(xiě)Properties配置文件(Properties類(lèi))
這篇文章主要介紹了Java如何讀寫(xiě)Properties配置文件(Properties類(lèi)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05SpringBoot實(shí)現(xiàn)自定義Starter的步驟詳解
在SpringBoot中,Starter是一種特殊的依賴(lài),它可以幫助我們快速地集成一些常用的功能,例如數(shù)據(jù)庫(kù)連接、消息隊(duì)列、Web框架等。在本文中,我們將介紹如何使用Spring Boot實(shí)現(xiàn)自定義Starter,需要的朋友可以參考下2023-06-06Java中Calendar日歷類(lèi)型常見(jiàn)方法詳解
Calendar是Java中常用的時(shí)間處理工具之一,它提供了很多日歷類(lèi)型常見(jiàn)方法,下面是一些常用的方法及對(duì)應(yīng)的代碼和運(yùn)行結(jié)果,感興趣的朋友一起看看吧2023-11-11Java Lock鎖多線程中實(shí)現(xiàn)流水線任務(wù)
這篇文章主要介紹了Java Lock鎖多線程中實(shí)現(xiàn)流水線任務(wù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05java 線程方法join簡(jiǎn)單用法實(shí)例總結(jié)
這篇文章主要介紹了java 線程方法join簡(jiǎn)單用法,結(jié)合實(shí)例形式總結(jié)分析了Java線程join方法的功能、原理及使用技巧,需要的朋友可以參考下2019-11-11