1. 程式人生 > 程式設計 >Spring Security基於方法級別的自定義表示式(可以完成任何許可權判斷)

Spring Security基於方法級別的自定義表示式(可以完成任何許可權判斷)

背景

需求是這樣的:專案採用的前後端分離的架構,且使用的RESTFUL風格API,同一個資源的相關請求是一樣的url,但是http method不一樣。

如果要允許一個人獲取某個資源,但是不能建立它,顯然基於url的許可權設計顯然是無法滿足需求的。

當我查閱到了可以基於方法的許可權控制之後,我認為這應該是個最佳方案。但是卻存在這樣一個問題,一個方法針對不同的入參可能會觸發不同的許可權。比如說,一個使用者擁有檢視A目錄的許可權,但是沒有檢視B目錄的許可權。而這兩個動作都是呼叫的同一個Controller方法,只是根據入參來區分檢視不同的目錄。

預設的hasAuthorityhasRole表示式都無法滿足需求,因為它們只能判斷一個硬編碼的許可權或者角色字串。所以我們需要用到自定義表示式來高度自定義許可權判斷以滿足需求。下面我們就來具體介紹如何使用它。

示例

我們將建立一個canRead的表示式。當入參為"A"時,將判斷當前使用者是否有檢視A的許可權;當入參為"B"時,將判斷當前使用者是否有檢視B的許可權。

配置

為了建立自定義表示式,我們首先需要實現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注入到表示式處理器內:

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。