自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Spring Security 中如何讓上級擁有下級的所有權(quán)限?

開發(fā) 項(xiàng)目管理
本文基于當(dāng)前 Spring Security 5.3.4 來分析,為什么要強(qiáng)調(diào)最新版呢?因?yàn)樵谠?5.0.11 版中,角色繼承配置和現(xiàn)在不一樣。舊版的方案我們現(xiàn)在不討論了,直接來看當(dāng)前最新版是怎么處理的。

[[341337]]

 本文基于當(dāng)前 Spring Security 5.3.4 來分析,為什么要強(qiáng)調(diào)最新版呢?因?yàn)樵谠?5.0.11 版中,角色繼承配置和現(xiàn)在不一樣。舊版的方案我們現(xiàn)在不討論了,直接來看當(dāng)前最新版是怎么處理的。

1.角色繼承案例

我們先來一個(gè)簡單的權(quán)限案例。

創(chuàng)建一個(gè) Spring Boot 項(xiàng)目,添加 Spring Security 依賴,并創(chuàng)建兩個(gè)測試用戶,如下:

  1. @Override 
  2. protected void configure(AuthenticationManagerBuilder auth) throws Exception { 
  3.     auth.inMemoryAuthentication() 
  4.             .withUser("javaboy"
  5.             .password("{noop}123").roles("admin"
  6.             .and() 
  7.             .withUser("江南一點(diǎn)雨"
  8.             .password("{noop}123"
  9.             .roles("user"); 

然后準(zhǔn)備三個(gè)測試接口,如下:

  1. @RestController 
  2. public class HelloController { 
  3.     @GetMapping("/hello"
  4.     public String hello() { 
  5.         return "hello"
  6.     } 
  7.  
  8.     @GetMapping("/admin/hello"
  9.     public String admin() { 
  10.         return "admin"
  11.     } 
  12.  
  13.     @GetMapping("/user/hello"
  14.     public String user() { 
  15.         return "user"
  16.     } 

這三個(gè)測試接口,我們的規(guī)劃是這樣的:

  1. /hello 是任何人都可以訪問的接口
  2. /admin/hello 是具有 admin 身份的人才能訪問的接口
  3. /user/hello 是具有 user 身份的人才能訪問的接口
  4. 所有 user 能夠訪問的資源,admin 都能夠訪問

注意第四條規(guī)范意味著所有具備 admin 身份的人自動具備 user 身份。

接下來我們來配置權(quán)限的攔截規(guī)則,在 Spring Security 的 configure(HttpSecurity http) 方法中,代碼如下:

  1. http.authorizeRequests() 
  2.         .antMatchers("/admin/**").hasRole("admin"
  3.         .antMatchers("/user/**").hasRole("user"
  4.         .anyRequest().authenticated() 
  5.         .and() 
  6.         ... 
  7.         ... 

這里的匹配規(guī)則我們采用了 Ant 風(fēng)格的路徑匹配符,Ant 風(fēng)格的路徑匹配符在 Spring 家族中使用非常廣泛,它的匹配規(guī)則也非常簡單:

通配符 含義
** 匹配多層路徑
* 匹配一層路徑
? 匹配任意單個(gè)字符

上面配置的含義是:

  1. 如果請求路徑滿足 /admin/** 格式,則用戶需要具備 admin 角色。
  2. 如果請求路徑滿足 /user/** 格式,則用戶需要具備 user 角色。
  3. 剩余的其他格式的請求路徑,只需要認(rèn)證(登錄)后就可以訪問。

注意代碼中配置的三條規(guī)則的順序非常重要,和 Shiro 類似,Spring Security 在匹配的時(shí)候也是按照從上往下的順序來匹配,一旦匹配到了就不繼續(xù)匹配了,所以攔截規(guī)則的順序不能寫錯(cuò)。

如果使用角色繼承,這個(gè)功能很好實(shí)現(xiàn),我們只需要在 SecurityConfig 中添加如下代碼來配置角色繼承關(guān)系即可:

  1. @Bean 
  2. RoleHierarchy roleHierarchy() { 
  3.     RoleHierarchyImpl hierarchy = new RoleHierarchyImpl(); 
  4.     hierarchy.setHierarchy("ROLE_admin > ROLE_user"); 
  5.     return hierarchy; 

注意,在配置時(shí),需要給角色手動加上 ROLE_ 前綴。上面的配置表示 ROLE_admin 自動具備 ROLE_user 的權(quán)限。

接下來,我們啟動項(xiàng)目進(jìn)行測試。

項(xiàng)目啟動成功后,我們首先以 江南一點(diǎn)雨的身份進(jìn)行登錄:

登錄成功后,分別訪問 /hello,/admin/hello 以及 /user/hello 三個(gè)接口,其中:

  1. /hello 因?yàn)榈卿浐缶涂梢栽L問,這個(gè)接口訪問成功。
  2. /admin/hello 需要 admin 身份,所以訪問失敗。
  3. /user/hello 需要 user 身份,所以訪問成功。

再以 javaboy 身份登錄,登錄成功后,我們發(fā)現(xiàn) javaboy 也能訪問 /user/hello 這個(gè)接口了,說明我們的角色繼承配置沒問題!

2.原理分析

這里配置的核心在于我們提供了一個(gè) RoleHierarchy 實(shí)例,所以我們的分析就從該類入手。

RoleHierarchy 是一個(gè)接口,該接口中只有一個(gè)方法:

  1. public interface RoleHierarchy { 
  2.  Collection<? extends GrantedAuthority> getReachableGrantedAuthorities( 
  3.    Collection<? extends GrantedAuthority> authorities); 
  4.  

這個(gè)方法參數(shù) authorities 是一個(gè)權(quán)限集合,從方法名上看方法的返回值是一個(gè)可訪問的權(quán)限集合。

舉個(gè)簡單的例子,假設(shè)角色層次結(jié)構(gòu)是 ROLE_A > ROLE_B > ROLE_C,現(xiàn)在直接給用戶分配的權(quán)限是 ROLE_A,但實(shí)際上用戶擁有的權(quán)限有 ROLE_A、ROLE_B 以及 ROLE_C。

getReachableGrantedAuthorities 方法的目的就是是根據(jù)角色層次定義,將用戶真正可以觸達(dá)的角色解析出來。

RoleHierarchy 接口有兩個(gè)實(shí)現(xiàn)類,如下圖:

  • NullRoleHierarchy 這是一個(gè)空的實(shí)現(xiàn),將傳入的參數(shù)原封不動返回。
  • RoleHierarchyImpl 這是我們上文所使用的實(shí)現(xiàn),這個(gè)會完成一些解析操作。

我們來重點(diǎn)看下 RoleHierarchyImpl 類。

這個(gè)類中實(shí)際上就四個(gè)方法setHierarchy、getReachableGrantedAuthorities、buildRolesReachableInOneStepMap以及 buildRolesReachableInOneOrMoreStepsMap,我們來逐個(gè)進(jìn)行分析。

首先是我們一開始調(diào)用的 setHierarchy 方法,這個(gè)方法用來設(shè)置角色層級關(guān)系:

  1. public void setHierarchy(String roleHierarchyStringRepresentation) { 
  2.  this.roleHierarchyStringRepresentation = roleHierarchyStringRepresentation; 
  3.  if (logger.isDebugEnabled()) { 
  4.   logger.debug("setHierarchy() - The following role hierarchy was set: " 
  5.     + roleHierarchyStringRepresentation); 
  6.  } 
  7.  buildRolesReachableInOneStepMap(); 
  8.  buildRolesReachableInOneOrMoreStepsMap(); 

用戶傳入的字符串變量設(shè)置給 roleHierarchyStringRepresentation 屬性,然后通過 buildRolesReachableInOneStepMap 和 buildRolesReachableInOneOrMoreStepsMap 方法完成對角色層級的解析。

buildRolesReachableInOneStepMap 方法用來將角色關(guān)系解析成一層一層的形式。我們來看下它的源碼:

  1. private void buildRolesReachableInOneStepMap() { 
  2.  this.rolesReachableInOneStepMap = new HashMap<>(); 
  3.  for (String line : this.roleHierarchyStringRepresentation.split("\n")) { 
  4.   String[] roles = line.trim().split("\\s+>\\s+"); 
  5.   for (int i = 1; i < roles.length; i++) { 
  6.    String higherRole = roles[i - 1]; 
  7.    GrantedAuthority lowerRole = new SimpleGrantedAuthority(roles[i]); 
  8.    Set<GrantedAuthority> rolesReachableInOneStepSet; 
  9.    if (!this.rolesReachableInOneStepMap.containsKey(higherRole)) { 
  10.     rolesReachableInOneStepSet = new HashSet<>(); 
  11.     this.rolesReachableInOneStepMap.put(higherRole, rolesReachableInOneStepSet); 
  12.    } else { 
  13.     rolesReachableInOneStepSet = this.rolesReachableInOneStepMap.get(higherRole); 
  14.    } 
  15.    rolesReachableInOneStepSet.add(lowerRole); 
  16.   } 
  17.  } 

首先大家看到,按照換行符來解析用戶配置的多個(gè)角色層級,這是什么意思呢?

我們前面案例中只是配置了 ROLE_admin > ROLE_user,如果你需要配置多個(gè)繼承關(guān)系,怎么配置呢?多個(gè)繼承關(guān)系用 \n 隔開即可,如下 ROLE_A > ROLE_B \n ROLE_C > ROLE_D。還有一種情況,如果角色層級關(guān)系是連續(xù)的,也可以這樣配置 ROLE_A > ROLE_B > ROLE_C > ROLE_D。

所以這里先用 \n 將多層繼承關(guān)系拆分開形成一個(gè)數(shù)組,然后對數(shù)組進(jìn)行遍歷。

在具體遍歷中,通過 > 將角色關(guān)系拆分成一個(gè)數(shù)組,然后對數(shù)組進(jìn)行解析,高一級的角色作為 key,低一級的角色作為 value。

代碼比較簡單,最終的解析出來存入 rolesReachableInOneStepMap 中的層級關(guān)系是這樣的:

假設(shè)角色繼承關(guān)系是 ROLE_A > ROLE_B \n ROLE_C > ROLE_D \n ROLE_C > ROLE_E,Map 中的數(shù)據(jù)是這樣:

  • A-->B
  • C-->[D,E]

假設(shè)角色繼承關(guān)系是 ROLE_A > ROLE_B > ROLE_C > ROLE_D,Map 中的數(shù)據(jù)是這樣:

  • A-->B
  • B-->C
  • C-->D

這是 buildRolesReachableInOneStepMap 方法解析出來的 rolesReachableInOneStepMap 集合。

接下來的 buildRolesReachableInOneOrMoreStepsMap 方法則是對 rolesReachableInOneStepMap 集合進(jìn)行再次解析,將角色的繼承關(guān)系拉平。

例如 rolesReachableInOneStepMap 中保存的角色繼承關(guān)系如下:

  • A-->B
  • B-->C
  • C-->D

經(jīng)過 buildRolesReachableInOneOrMoreStepsMap 方法解析之后,新的 Map 中保存的數(shù)據(jù)如下:

  • A-->[B、C、D]
  • B-->[C、D]
  • C-->D

這樣解析完成后,每一個(gè)角色可以觸達(dá)到的角色就一目了然了。

我們來看下 buildRolesReachableInOneOrMoreStepsMap 方法的實(shí)現(xiàn)邏輯:

  1. private void buildRolesReachableInOneOrMoreStepsMap() { 
  2.  this.rolesReachableInOneOrMoreStepsMap = new HashMap<>(); 
  3.  for (String roleName : this.rolesReachableInOneStepMap.keySet()) { 
  4.   Set<GrantedAuthority> rolesToVisitSet = new HashSet<>(this.rolesReachableInOneStepMap.get(roleName)); 
  5.   Set<GrantedAuthority> visitedRolesSet = new HashSet<>(); 
  6.   while (!rolesToVisitSet.isEmpty()) { 
  7.    GrantedAuthority lowerRole = rolesToVisitSet.iterator().next(); 
  8.    rolesToVisitSet.remove(lowerRole); 
  9.    if (!visitedRolesSet.add(lowerRole) || 
  10.      !this.rolesReachableInOneStepMap.containsKey(lowerRole.getAuthority())) { 
  11.     continue
  12.    } else if (roleName.equals(lowerRole.getAuthority())) { 
  13.     throw new CycleInRoleHierarchyException(); 
  14.    } 
  15.    rolesToVisitSet.addAll(this.rolesReachableInOneStepMap.get(lowerRole.getAuthority())); 
  16.   } 
  17.   this.rolesReachableInOneOrMoreStepsMap.put(roleName, visitedRolesSet); 
  18.  } 

這個(gè)方法還比較巧妙。首先根據(jù) roleName 從 rolesReachableInOneStepMap 中獲取對應(yīng)的 rolesToVisitSet,這個(gè) rolesToVisitSet 是一個(gè) Set 集合,對其進(jìn)行遍歷,將遍歷結(jié)果添加到 visitedRolesSet 集合中,如果 rolesReachableInOneStepMap 集合的 key 不包含當(dāng)前讀取出來的 lowerRole,說明這個(gè) lowerRole 就是整個(gè)角色體系中的最底層,直接 continue。否則就把 lowerRole 在 rolesReachableInOneStepMap 中對應(yīng)的 value 拿出來繼續(xù)遍歷。

最后將遍歷結(jié)果存入 rolesReachableInOneOrMoreStepsMap 集合中即可。

這個(gè)方法有點(diǎn)繞,小伙伴們可以自己打個(gè)斷點(diǎn)品一下。

看了上面的分析,小伙伴們可能發(fā)現(xiàn)了,其實(shí)角色繼承,最終還是拉平了去對比。

我們定義的角色有層級,但是代碼中又將這種層級拉平了,方便后續(xù)的比對。

最后還有一個(gè) getReachableGrantedAuthorities 方法,根據(jù)傳入的角色分析出其可能潛在包含的一些角色:

  1. public Collection<GrantedAuthority> getReachableGrantedAuthorities( 
  2.   Collection<? extends GrantedAuthority> authorities) { 
  3.  if (authorities == null || authorities.isEmpty()) { 
  4.   return AuthorityUtils.NO_AUTHORITIES; 
  5.  } 
  6.  Set<GrantedAuthority> reachableRoles = new HashSet<>(); 
  7.  Set<String> processedNames = new HashSet<>(); 
  8.  for (GrantedAuthority authority : authorities) { 
  9.   if (authority.getAuthority() == null) { 
  10.    reachableRoles.add(authority); 
  11.    continue
  12.   } 
  13.   if (!processedNames.add(authority.getAuthority())) { 
  14.    continue
  15.   } 
  16.   reachableRoles.add(authority); 
  17.   Set<GrantedAuthority> lowerRoles = this.rolesReachableInOneOrMoreStepsMap.get(authority.getAuthority()); 
  18.   if (lowerRoles == null) { 
  19.    continue
  20.   } 
  21.   for (GrantedAuthority role : lowerRoles) { 
  22.    if (processedNames.add(role.getAuthority())) { 
  23.     reachableRoles.add(role); 
  24.    } 
  25.   } 
  26.  } 
  27.  List<GrantedAuthority> reachableRoleList = new ArrayList<>(reachableRoles.size()); 
  28.  reachableRoleList.addAll(reachableRoles); 
  29.  return reachableRoleList; 

這個(gè)方法的邏輯比較直白,就是從 rolesReachableInOneOrMoreStepsMap 集合中查詢出當(dāng)前角色真正可訪問的角色信息。

3.RoleHierarchyVoter

getReachableGrantedAuthorities 方法將在 RoleHierarchyVoter 投票器中被調(diào)用。

  1. public class RoleHierarchyVoter extends RoleVoter { 
  2.  private RoleHierarchy roleHierarchy = null
  3.  public RoleHierarchyVoter(RoleHierarchy roleHierarchy) { 
  4.   Assert.notNull(roleHierarchy, "RoleHierarchy must not be null"); 
  5.   this.roleHierarchy = roleHierarchy; 
  6.  } 
  7.  @Override 
  8.  Collection<? extends GrantedAuthority> extractAuthorities( 
  9.    Authentication authentication) { 
  10.   return roleHierarchy.getReachableGrantedAuthorities(authentication 
  11.     .getAuthorities()); 
  12.  } 

關(guān)于 Spring Security 投票器,將是另外一個(gè)故事,松哥將在下篇文章中和小伙伴們分享投票器和決策器~

4.小結(jié)

好啦,今天就和小伙伴們簡簡單單聊一下角色繼承的問題,感興趣的小伙伴可以自己試一下~如果覺得有收獲,記得點(diǎn)個(gè)在看鼓勵(lì)下松哥哦~

本文轉(zhuǎn)載自微信公眾號「 江南一點(diǎn)雨」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 江南一點(diǎn)雨公眾號。

 

 

責(zé)任編輯:武曉燕 來源: 江南一點(diǎn)雨
相關(guān)推薦

2011-01-07 09:19:35

Linux文件權(quán)限

2024-03-19 14:43:55

Rust編譯所有權(quán)

2022-11-03 15:14:43

Linux文件權(quán)限

2023-01-27 14:47:30

DevOps軟件開發(fā)工程師

2009-11-28 20:21:14

2011-01-20 07:50:51

Linux文件系統(tǒng)管理所有權(quán)

2017-07-27 13:34:52

Rust所有權(quán)數(shù)據(jù)

2022-05-30 00:19:13

元宇宙NFTWeb3

2022-03-18 08:00:00

區(qū)塊鏈代幣以太坊

2024-01-10 09:26:52

Rust所有權(quán)編程

2020-09-16 08:07:54

權(quán)限粒度Spring Secu

2018-12-14 10:08:23

物聯(lián)網(wǎng)訂閱IOT

2021-07-30 05:12:54

智能指針C++編程語言

2024-09-02 10:40:18

2024-04-24 12:41:10

Rust安全性內(nèi)存

2013-08-16 10:46:20

2018-01-23 11:15:28

云計(jì)算數(shù)據(jù)平臺云平臺

2023-04-10 16:25:37

區(qū)塊鏈去中心化安全

2017-10-23 12:42:42

2009-09-12 09:46:47

Windows 7所有權(quán)添加
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號