• 當前位置:首頁 > IT技術 > 編程語言 > 正文

    Spring Security實現類似shiro權限表達式的RBAC權限控制
    2022-04-29 13:56:15

    昨天有個粉絲加了我,問我如何實現類似shiro的資源權限表達式的訪問控制。我以前有一個小框架用的就是shiro,權限控制就用了資源權限表達式,所以這個東西對我不陌生,但是在Spring Security中我并沒有使用過它,不過我認為Spring Security可以實現這一點。是的,我找到了實現它的方法。

    ?

    資源權限表達式

    說了這么多,我覺得應該解釋一下什么叫資源權限表達式。權限控制的核心就是清晰地表達出特定資源的某種操作,一個格式良好好的權限聲明可以清晰表達出用戶對該資源擁有的操作權限。

    通常一個資源在系統中的標識是唯一的,比如User用來標識用戶,ORDER標識訂單。不管什么資源大都可以歸納出以下這幾種操作

    Spring Security實現類似shiro權限表達式的RBAC權限控制_python

    在 shiro權限聲明通常對上面的這種資源操作關系用冒號分隔的方式進行表示。例如讀取用戶信息的操作表示為??USER:READ???,甚至還可以更加細一些,用??USER:READ:123??表示讀取ID為??123??的用戶權限。

    資源操作定義好了,再把它和角色關聯起來不就是基于RBAC的權限資源控制了嗎?就像下面這樣:

    Spring Security實現類似shiro權限表達式的RBAC權限控制_設計模式_02

    這樣資源和角色的關系可以進行CRUD操作進行動態綁定。

    Spring Security中的實現

    資源權限表達式的動態權限控制在Spring Security也是可以實現的。首先開啟方法級別的注解安全控制。

    /**
    * 開啟方法安全注解
    *
    * @author felord.cn
    */
    @EnableGlobalMethodSecurity(prePostEnabled = true,
    securedEnabled = true,
    jsr250Enabled = true)
    public class MethodSecurityConfig {

    }

    MethodSecurityExpressionHandler

    ??MethodSecurityExpressionHandler??? 提供了一個對方法進行安全訪問的門面擴展。它的實現類??DefaultMethodSecurityExpressionHandler??更是提供了針對方法的一系列擴展接口,這里我總結了一下:

    Spring Security實現類似shiro權限表達式的RBAC權限控制_設計模式_03

    這里的??PermissionEvaluator??正好可以滿足需要。

    PermissionEvaluator

    ??PermissionEvaluator?? 接口抽象了對一個用戶是否有權限訪問一個特定的領域對象的評估過程。

    public interface PermissionEvaluator extends AopInfrastructureBean {


    boolean hasPermission(Authentication authentication,
    Object targetDomainObject, Object permission);


    boolean hasPermission(Authentication authentication,
    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);
    }

    @Override
    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')??來靜態綁定該接口的訪問權限表達式:

    @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???。其它關于SpringSecurity注解的知識可以看?Spring Security實現類似shiro權限表達式的RBAC權限控制_shiro_04這一篇。??

    留給你去測試的

    你可以看看注解改成這樣會是什么效果:

    @PreAuthorize("hasPermission('USER','READ') or hasRole('ADMIN')")

    這樣呢?

    @PreAuthorize("hasPermission('1234','USER','READ')")

    或者讓??targetId??動態化:

    @PreAuthorize("hasPermission(#id,'USER','READ')")
    public Collection<String> postfilter(String id){

    }

    可以留言區回復測試結果

    另外最近胖哥有很多成系列的內容輸出:

    • OAuth2?系列教程,已經更新了40多篇。

    • 開源了一個登錄組件擴展spring-security-login-extension,降低對接配置成本,歡迎學習、star。
    • 粉絲福利:正版IntelliJ IDEA?抽獎

    有興趣的可以看看。




    本文摘自 :https://blog.51cto.com/u

    開通會員,享受整站包年服務
    国产呦精品一区二区三区网站|久久www免费人咸|精品无码人妻一区二区|久99久热只有精品国产15|中文字幕亚洲无线码