中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring Security中怎么讓上級擁有下級的所有權限

發布時間:2021-12-07 13:38:27 來源:億速云 閱讀:116 作者:iii 欄目:大數據

本篇內容介紹了“Spring Security中怎么讓上級擁有下級的所有權限”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

1.角色繼承案例

我們先來一個簡單的權限案例。

創建一個 Spring Boot 項目,添加 Spring Security 依賴,并創建兩個測試用戶,如下:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
            .withUser("javaboy")
            .password("{noop}123").roles("admin")
            .and()
            .withUser("江南一點雨")
            .password("{noop}123")
            .roles("user");
}
 

然后準備三個測試接口,如下:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

    @GetMapping("/admin/hello")
    public String admin() {
        return "admin";
    }

    @GetMapping("/user/hello")
    public String user() {
        return "user";
    }
}
 

這三個測試接口,我們的規劃是這樣的:

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

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

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

http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")
        .antMatchers("/user/**").hasRole("user")
        .anyRequest().authenticated()
        .and()
        ...
        ...
 

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

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

上面配置的含義是:

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

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

如果使用角色繼承,這個功能很好實現,我們只需要在 SecurityConfig 中添加如下代碼來配置角色繼承關系即可:

@Bean
RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_admin > ROLE_user");
    return hierarchy;
}
 

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

接下來,我們啟動項目進行測試。

項目啟動成功后,我們首先以 江南一點雨的身份進行登錄:

Spring Security中怎么讓上級擁有下級的所有權限  

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

  1. /hello 因為登錄后就可以訪問,這個接口訪問成功。
  2. /admin/hello 需要 admin 身份,所以訪問失敗。
  3. /user/hello 需要 user 身份,所以訪問成功。

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

 

2.原理分析

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

RoleHierarchy 是一個接口,該接口中只有一個方法:

public interface RoleHierarchy {
 Collection<? extends GrantedAuthority> getReachableGrantedAuthorities(
   Collection<? extends GrantedAuthority> authorities);

}
 

這個方法參數 authorities 是一個權限集合,從方法名上看方法的返回值是一個可訪問的權限集合。

舉個簡單的例子,假設角色層次結構是 ROLE_A > ROLE_B > ROLE_C,現在直接給用戶分配的權限是 ROLE_A,但實際上用戶擁有的權限有 ROLE_AROLE_B 以及 ROLE_C

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

RoleHierarchy 接口有兩個實現類,如下圖:

Spring Security中怎么讓上級擁有下級的所有權限  
  • NullRoleHierarchy 這是一個空的實現,將傳入的參數原封不動返回。
  • RoleHierarchyImpl 這是我們上文所使用的實現,這個會完成一些解析操作。

我們來重點看下 RoleHierarchyImpl 類。

這個類中實際上就四個方法 setHierarchygetReachableGrantedAuthoritiesbuildRolesReachableInOneStepMap 以及 buildRolesReachableInOneOrMoreStepsMap,我們來逐個進行分析。

首先是我們一開始調用的 setHierarchy 方法,這個方法用來設置角色層級關系:

public void setHierarchy(String roleHierarchyStringRepresentation) {
 this.roleHierarchyStringRepresentation = roleHierarchyStringRepresentation;
 if (logger.isDebugEnabled()) {
  logger.debug("setHierarchy() - The following role hierarchy was set: "
    + roleHierarchyStringRepresentation);
 }
 buildRolesReachableInOneStepMap();
 buildRolesReachableInOneOrMoreStepsMap();
}
 

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

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

private void buildRolesReachableInOneStepMap() {
 this.rolesReachableInOneStepMap = new HashMap<>();
 for (String line : this.roleHierarchyStringRepresentation.split("\n")) {
  String[] roles = line.trim().split("\\s+>\\s+");
  for (int i = 1; i < roles.length; i++) {
   String higherRole = roles[i - 1];
   GrantedAuthority lowerRole = new SimpleGrantedAuthority(roles[i]);
   Set<GrantedAuthority> rolesReachableInOneStepSet;
   if (!this.rolesReachableInOneStepMap.containsKey(higherRole)) {
    rolesReachableInOneStepSet = new HashSet<>();
    this.rolesReachableInOneStepMap.put(higherRole, rolesReachableInOneStepSet);
   } else {
    rolesReachableInOneStepSet = this.rolesReachableInOneStepMap.get(higherRole);
   }
   rolesReachableInOneStepSet.add(lowerRole);
  }
 }
}
 

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

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

所以這里先用 \n 將多層繼承關系拆分開形成一個數組,然后對數組進行遍歷。

在具體遍歷中,通過 > 將角色關系拆分成一個數組,然后對數組進行解析,高一級的角色作為 key,低一級的角色作為 value。

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

假設角色繼承關系是 ROLE_A > ROLE_B \n ROLE_C > ROLE_D \n ROLE_C > ROLE_E,Map 中的數據是這樣:

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

假設角色繼承關系是 ROLE_A > ROLE_B > ROLE_C > ROLE_D,Map 中的數據是這樣:

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

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

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

例如 rolesReachableInOneStepMap 中保存的角色繼承關系如下:

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

經過 buildRolesReachableInOneOrMoreStepsMap 方法解析之后,新的 Map 中保存的數據如下:

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

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

我們來看下 buildRolesReachableInOneOrMoreStepsMap 方法的實現邏輯:

private void buildRolesReachableInOneOrMoreStepsMap() {
 this.rolesReachableInOneOrMoreStepsMap = new HashMap<>();
 for (String roleName : this.rolesReachableInOneStepMap.keySet()) {
  Set<GrantedAuthority> rolesToVisitSet = new HashSet<>(this.rolesReachableInOneStepMap.get(roleName));
  Set<GrantedAuthority> visitedRolesSet = new HashSet<>();
  while (!rolesToVisitSet.isEmpty()) {
   GrantedAuthority lowerRole = rolesToVisitSet.iterator().next();
   rolesToVisitSet.remove(lowerRole);
   if (!visitedRolesSet.add(lowerRole) ||
     !this.rolesReachableInOneStepMap.containsKey(lowerRole.getAuthority())) {
    continue;
   } else if (roleName.equals(lowerRole.getAuthority())) {
    throw new CycleInRoleHierarchyException();
   }
   rolesToVisitSet.addAll(this.rolesReachableInOneStepMap.get(lowerRole.getAuthority()));
  }
  this.rolesReachableInOneOrMoreStepsMap.put(roleName, visitedRolesSet);
 }
}
 

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

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

這個方法有點繞,小伙伴們可以自己打個斷點品一下。

看了上面的分析,小伙伴們可能發現了,其實角色繼承,最終還是拉平了去對比。

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

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

public Collection<GrantedAuthority> getReachableGrantedAuthorities(
  Collection<? extends GrantedAuthority> authorities) {
 if (authorities == null || authorities.isEmpty()) {
  return AuthorityUtils.NO_AUTHORITIES;
 }
 Set<GrantedAuthority> reachableRoles = new HashSet<>();
 Set<String> processedNames = new HashSet<>();
 for (GrantedAuthority authority : authorities) {
  if (authority.getAuthority() == null) {
   reachableRoles.add(authority);
   continue;
  }
  if (!processedNames.add(authority.getAuthority())) {
   continue;
  }
  reachableRoles.add(authority);
  Set<GrantedAuthority> lowerRoles = this.rolesReachableInOneOrMoreStepsMap.get(authority.getAuthority());
  if (lowerRoles == null) {
   continue;
  }
  for (GrantedAuthority role : lowerRoles) {
   if (processedNames.add(role.getAuthority())) {
    reachableRoles.add(role);
   }
  }
 }
 List<GrantedAuthority> reachableRoleList = new ArrayList<>(reachableRoles.size());
 reachableRoleList.addAll(reachableRoles);
 return reachableRoleList;
}
 

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

 

3.RoleHierarchyVoter

getReachableGrantedAuthorities 方法將在 RoleHierarchyVoter 投票器中被調用。

public class RoleHierarchyVoter extends RoleVoter {
 private RoleHierarchy roleHierarchy = null;
 public RoleHierarchyVoter(RoleHierarchy roleHierarchy) {
  Assert.notNull(roleHierarchy, "RoleHierarchy must not be null");
  this.roleHierarchy = roleHierarchy;
 }
 @Override
 Collection<? extends GrantedAuthority> extractAuthorities(
   Authentication authentication) {
  return roleHierarchy.getReachableGrantedAuthorities(authentication
    .getAuthorities());
 }
}

“Spring Security中怎么讓上級擁有下級的所有權限”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

安乡县| 大姚县| 灵台县| 阿荣旗| 油尖旺区| 眉山市| 栾城县| 扎囊县| 滨州市| 壤塘县| 揭东县| 奉贤区| 双流县| 甘德县| 翁牛特旗| 南丹县| 遂宁市| 英超| 丰镇市| 柏乡县| 玉龙| 祁阳县| 富顺县| 灵台县| 太和县| 育儿| 云和县| 浦东新区| 包头市| 留坝县| 沐川县| 兰西县| 察雅县| 会昌县| 石泉县| 鄢陵县| 莫力| 舒兰市| 林州市| 仲巴县| 盘锦市|