文章目錄
-
-
- 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方法,隻是根據入參來區分檢視不同的目錄。
預設的
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
OAuth2AuthenticationProcessingFilter 過濾器攔截擷取使用者認證資訊這塊就不分析,前面已經分析很多遍了。
① 請求進入到自定義權限表達式類 CustomSecurityExpressionRoot的canRead方法 :
② 調用父類SecurityExpressionRoot的hasAuthority方法,該方法會繼續調用父類的SecurityExpressionRoot的hasAnyAuthority方法:
③ 在hasAnyAuthority方法中調用 hasAnyAuthorityName 方法判斷登入使用者是否具備knowledgeEdit權限:
④ roleSet是使用者具備的所有權限,可以看到目前登入使用者具有knowledgeEdit權限,是以傳回true:
⑤ 回到 CustomSecurityExpressionRoot 類的canRead方法,傳回true
⑥ 請求進入 DocController的 getDocList方法:
6. 啟動項目測試 foo=B
① 請求進入到自定義權限表達式類 CustomSecurityExpressionRoot的canRead方法 :
② 調用父類SecurityExpressionRoot的hasAuthority方法,該方法會繼續調用父類的SecurityExpressionRoot的hasAnyAuthority方法:
③ 在hasAnyAuthority方法中調用 hasAnyAuthorityName 方法判斷登入使用者是否具備roleEdit權限,roleSet是使用者具備的所有權限,可以看到目前登入使用者不具有roleEdit權限,是以傳回false:
④ 回到 CustomSecurityExpressionRoot 類的canRead方法,傳回false