Spring Security基於方法級別的自定義表達式(能夠完成任何權限判斷)

背景

需求是這樣的:項目採用的先後端分離的架構,且使用的RESTFUL風格API,同一個資源的相關請求是同樣的url,可是http method不同。java

若是要容許一我的獲取某個資源,可是不能建立它,顯然基於url的權限設計顯然是沒法知足需求的。express

當我查閱到了能夠基於方法的權限控制以後,我認爲這應該是個最佳方案。可是卻存在這樣一個問題,一個方法針對不一樣的入參可能會觸發不一樣的權限。好比說,一個用戶擁有查看A目錄的權限,可是沒有查看B目錄的權限。而這兩個動做都是調用的同一個Controller方法,只是根據入參來區分查看不一樣的目錄。後端

默認的hasAuthorityhasRole表達式都沒法知足需求,由於它們只能判斷一個硬編碼的權限或者角色字符串。因此咱們須要用到自定義表達式來高度自定義權限判斷以知足需求。下面咱們就來具體介紹如何使用它。安全

示例

咱們將建立一個canRead的表達式。當入參爲"A"時,將判斷當前用戶是否有查看A的權限;當入參爲"B"時,將判斷當前用戶是否有查看B的權限。bash

配置

爲了建立自定義表達式,咱們首先須要實現root表達式:架構

public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    private Object filterObject;
    private Object returnObject;

    public CustomMethodSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    // 咱們的自定義表達式
    public boolean canRead(String foo) {
        if (foo.equals("A") && !this.hasAuthority("CAN_READ_A")) {
            return false;
        }

        if (foo.equals("B") && !this.hasAuthority("CAN_READ_B")) {
            return false;
        }
        
        return true;
    }

    @Override
    public Object getFilterObject() {
        return this.filterObject;
    }

    @Override
    public Object getReturnObject() {
        return this.returnObject;
    }

    @Override
    public Object getThis() {
        return this;
    }

    @Override
    public void setFilterObject(Object obj) {
        this.filterObject = obj;
    }

    @Override
    public void setReturnObject(Object obj) {
        this.returnObject = obj;
    }
}
複製代碼

接下來,咱們須要把CustomMethodSecurityExpressionRoot注入到表達式處理器內:app

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
    private AuthenticationTrustResolver trustResolver = 
      new AuthenticationTrustResolverImpl();
 
    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot( Authentication authentication, MethodInvocation invocation) {
        CustomMethodSecurityExpressionRoot root = 
          new CustomMethodSecurityExpressionRoot(authentication);
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setTrustResolver(this.trustResolver);
        root.setRoleHierarchy(getRoleHierarchy());
        return root;
    }
}
複製代碼

而後須要把CustomMethodSecurityExpressionHandler寫到方法安全配置裏面:前後端分離

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        CustomMethodSecurityExpressionHandler expressionHandler = 
          new CustomMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator());
        return expressionHandler;
    }
}
複製代碼

使用

@PreAuthorize("canRead(#foo)")
@GetMapping("/")
public Foo getFoo(@RequestParam("foo") String foo) {
    return fooService.findAll(foo);
}
複製代碼

若是用戶訪問A,可是沒有CAN_READ_A權限,接口將會返回403。ide

相關文章
相關標籤/搜索