天天看點

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

文章目錄

      • 1. 自定義權限表達式類 CustomSecurityExpressionRoot
      • 2. 表達式處理器 CustomSecurityExpressionRootHandler
      • 3. 資源服務配置類 ResourceServerAutoConfiguration
      • 4. 使用自定義權限表達式 DocController
      • 5. 啟動項目測試 foo=A
      • 6. 啟動項目測試 foo=B

相關文章:SpringSecurity Oauth2實戰 - 08 SpEL權限表達式源碼及兩種權限控制方式原理

參考文章:http://www.liuhaihua.cn/archives/596471.html

上一講我們分析了SpEL權限表達式的實作原理以及相關的源碼,然後以debug的方式介紹了基于url的權限表達式和基于注解的權限表達式的調用流程,不管哪種方式權限表達式對應的都是 SecurityExpressionRoot 中方法。繼續基于上一講的内容研究如何自定義權限表達式。

一個方法針對不同的入參可能會觸發不同的權限。比如說,一個使用者擁有檢視A目錄的權限,但是沒有檢視B目錄的權限。而這兩個動作都是調用的同一個Controller方法,隻是根據入參來區分檢視不同的目錄。

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

預設的

hasAuthority

hasRole

表達式無法滿足需求,因為它們隻能判斷一個寫死的權限或者角色字元串。是以我們需要用到自定義表達式來自定義權限判斷以滿足需求。 我們将建立一個

canRead

的表達式。當入參為"A"時,将判斷目前使用者是否有檢視A的權限;當入參為"B"時,将判斷目前使用者是否有檢視B的權限。

我們知道,在 @PreAuthorize 注解中使用的 hasAuthority、hasPermission、hasRole、hasAnyRole 等權限表達式都是由 SecurityExpressionRoot 及其子類提供的,準确來說是由 MethodSecurityExpressionRoot 類提供的,該類中的方法就是可以在 @PreAuthorize 注解中使用的SpEl權限表達式。

而自定義權限表達式就是在已有方法上繼續擴充新方法,我們可以像 MethodSecurityExpressionRoot 類一樣,自定義類繼承 SecurityExpressionRoot 類并實作 MethodSecurityExpressionOperations 接口,在該對自定義類中繼續添加新的方法,進而實作自定義權限表達式。

1. 自定義權限表達式類 CustomSecurityExpressionRoot

public class CustomSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    /**
     * MethodSecurityExpressionOperations 接口方法的屬性
     */
    private Object filterObject;
    private Object returnObject;
    private Object target;

    /**
     * 添加一個新的方法,這個方法就是我們自定義的權限表達式
     */
    public boolean canRead(String foo) {
        if (foo.equals("A") && !this.hasAuthority("knowledgeEdit")) {
            return false;
        }

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

    /**
     * 構造方法
     */
    public CustomSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    /**
     * 下面的方法都是 MethodSecurityExpressionOperations 接口中的實作方法,沒有更改
     */

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

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

    @Override
    public void setReturnObject(Object returnObject) {
        this.returnObject = returnObject;
    }

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

    void setThis(Object target) {
        this.target = target;
    }

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

2. 表達式處理器 CustomSecurityExpressionRootHandler

把 CustomSecurityExpressionRoot 注入到表達式處理器

public class CustomSecurityExpressionRootHandler extends DefaultMethodSecurityExpressionHandler {
    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
        CustomSecurityExpressionRoot customSecurityExpressionRoot = new CustomSecurityExpressionRoot(authentication);

        customSecurityExpressionRoot.setThis(invocation.getThis());
        customSecurityExpressionRoot.setPermissionEvaluator(getPermissionEvaluator());
        customSecurityExpressionRoot.setTrustResolver(getTrustResolver());
        customSecurityExpressionRoot.setRoleHierarchy(getRoleHierarchy());
        customSecurityExpressionRoot.setDefaultRolePrefix(getDefaultRolePrefix());
        return customSecurityExpressionRoot;
    }
}
           

3. 資源服務配置類 ResourceServerAutoConfiguration

資源服務配置類中添加 CustomSecurityExpressionRootHandler

@Slf4j
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {
    /**
     * 權限表達式的自定義處理
     */
    @Autowired
    private GlobalMethodSecurityConfiguration globalMethodSecurityConfiguration;

    @Autowired
    private WhiteUrlAutoConfiguration whiteUrlAutoConfiguration;

    @Autowired
    private TokenStore tokenStore;

    @Value("${spring.application.name}")
    private String appName;
   
    /**
     * 自定義權限表達式處理
     */
    @Bean
    public GlobalMethodSecurityConfiguration globalMethodSecurityConfiguration() {
        List<MethodSecurityExpressionHandler> handlers = new ArrayList<>(1);
        handlers.add(customMethodSecurityExpressionHandler());
        globalMethodSecurityConfiguration.setMethodSecurityExpressionHandler(handlers);
        return globalMethodSecurityConfiguration;
    }

    @Bean
    public MethodSecurityExpressionHandler customMethodSecurityExpressionHandler() {
        CustomSecurityExpressionRootHandler expressionHandler = new CustomSecurityExpressionRootHandler();
        return expressionHandler;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(appName);
        resources.tokenStore(tokenStore);
        resources.tokenExtractor(tokenExtractor());
    }

    @Bean
    @Primary
    public TokenExtractor tokenExtractor() {
        CustomTokenExtractor customTokenExtractor = new CustomTokenExtractor();
        return customTokenExtractor;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // http.authorizeRequests()主要是對url進行通路權限控制,通過這個方法來實作url授權操作
        http.authorizeRequests()
                // permitAll()權限表達式
                .antMatchers("/api/v1/login", "/api/v1/token").permitAll();
        // 其他請求隻要認證後的使用者就可以通路
        http.authorizeRequests().anyRequest().authenticated();
        http.formLogin().disable();
        http.httpBasic().disable();
    }
}
           

4. 使用自定義權限表達式 DocController

@RestController
@RequestMapping("/api/v1")
public class DocController {

    @PreAuthorize("canRead(#foo)")
    @GetMapping("/doc")
    public String getDocList(@RequestParam("foo") String foo){
        return foo;
    }
}
           

5. 啟動項目測試 foo=A

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

OAuth2AuthenticationProcessingFilter 過濾器攔截擷取使用者認證資訊這塊就不分析,前面已經分析很多遍了。

① 請求進入到自定義權限表達式類 CustomSecurityExpressionRoot的canRead方法 :

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

② 調用父類SecurityExpressionRoot的hasAuthority方法,該方法會繼續調用父類的SecurityExpressionRoot的hasAnyAuthority方法:

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

③ 在hasAnyAuthority方法中調用 hasAnyAuthorityName 方法判斷登入使用者是否具備knowledgeEdit權限:

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

④ roleSet是使用者具備的所有權限,可以看到目前登入使用者具有knowledgeEdit權限,是以傳回true:

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

⑤ 回到 CustomSecurityExpressionRoot 類的canRead方法,傳回true

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

⑥ 請求進入 DocController的 getDocList方法:

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

6. 啟動項目測試 foo=B

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

① 請求進入到自定義權限表達式類 CustomSecurityExpressionRoot的canRead方法 :

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

② 調用父類SecurityExpressionRoot的hasAuthority方法,該方法會繼續調用父類的SecurityExpressionRoot的hasAnyAuthority方法:

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

③ 在hasAnyAuthority方法中調用 hasAnyAuthorityName 方法判斷登入使用者是否具備roleEdit權限,roleSet是使用者具備的所有權限,可以看到目前登入使用者不具有roleEdit權限,是以傳回false:

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式

④ 回到 CustomSecurityExpressionRoot 類的canRead方法,傳回false

SpringSecurity Oauth2 - 09 自定義SpEL權限表達式
SpringSecurity Oauth2 - 09 自定義SpEL權限表達式