
1. 前言
歡迎閱讀 Spring Security 實戰干貨[1] 系列文章 。在上一篇 基於配置的接口角色訪問控制[2] 我們講解了如何通過 javaConfig 的方式配置接口的角色訪問控制。其實還有一種更加靈活的配置方式 基於注解 。今天我們就來探討一下。DEMO 獲取方式在文末。
2. Spring Security 方法安全
Spring Security 基於注解的安全認證是通過在相關的方法上進行安全注解標記來實現的。
2.1 開啟全局方法安全
我們可以在任何 @Configuration
實例上使用 @EnableGlobalMethodSecurity
注解來啟用全局方法安全注解功能。該注解提供了三種不同的機制來實現同一種功能,所以我們單獨開一章進行探討。
3. @EnableGlobalMethodSecurity 注解
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ GlobalMethodSecuritySelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {
/**
* 基於表達式進行方法訪問控制
*/
boolean prePostEnabled() default false;
/**
* 基於 @Secured 注解
*/
boolean securedEnabled() default false;
/**
* 基於 JSR-250 注解
*/
boolean jsr250Enabled() default false;
boolean proxyTargetClass() default false;
int order() default Ordered.LOWEST_PRECEDENCE;
}
@EnableGlobalMethodSecurity
源碼中提供了 prePostEnabled
、securedEnabled
和 jsr250Enabled
三種方式。當你開啟全局基於注解的方法安全功能時,也就是使用 @EnableGlobalMethodSecurity
注解時我們需要選擇使用這三種的一種或者其中幾種。我們接下來將分別介紹它們。
4. 使用 prePostEnabled
如果你在 @EnableGlobalMethodSecurity
設置 prePostEnabled
為 true
,則開啟了基於表達式的方法安全控制。通過表達式運算結果的布爾值來決定是否可以訪問(true
開放, false
拒絕 )。有時您可能需要執行開啟 prePostEnabled
復雜的操作。對於這些實例,您可以擴展 GlobalMethodSecurityConfiguration
,確保子類上存在@EnableGlobalMethodSecurity(prePostEnabled = true)
。例如,如果要提供自定義 MethodSecurityExpressionHandler
:
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
// ... create and return custom MethodSecurityExpressionHandler ...
return expressionHandler;
}
}
上面示例屬於高級操作,一般沒有必要。無論是否繼承GlobalMethodSecurityConfiguration
都將會開啟四個注解。@PreAuthorize
和 @PostAuthorize
側重於方法調用的控制;而 @PreFilter
和 @PostFilter
側重於數據的控制。
4.1 @PreAuthorize
在標記的方法調用之前,通過表達式來計算是否可以授權訪問。接下來我來總結以下常用的表達式。
-
基於
SecurityExpressionOperations
接口的表達式,也就是我們在 上一文 [3]的javaConfig
配置。示例:@PreAuthorize("hasRole('ADMIN')")
必須擁有ROLE_ADMIN
角色。 -
基於
UserDetails
的表達式,此表達式用以對當前用戶的一些額外的限定操作。示例:@PreAuthorize("principal.username.startsWith('Felordcn')")
用戶名開頭為Felordcn
的用戶才能訪問。 -
基於對入參的
SpEL
表達式處理。關於SpEL
表達式可參考官方文檔。或者通過關注公眾號:Felordcn 來獲取相關資料。示例:@PreAuthorize("#id.equals(principal.username)")
入參id
必須同當前的用戶名相同。
4.2 @PostAuthorize
在標記的方法調用之后,通過表達式來計算是否可以授權訪問。該注解是針對 @PreAuthorize
。區別在於先執行方法。而后進行表達式判斷。如果方法沒有返回值實際上等於開放權限控制;如果有返回值實際的結果是用戶操作成功但是得不到響應。
4.3 @PreFilter
基於方法入參相關的表達式,對入參進行過濾。分頁慎用!該過程發生在接口接收參數之前。 入參必須為 java.util.Collection
且支持 remove(Object)
的參數。如果有多個集合需要通過 filterTarget=<參數名>
來指定過濾的集合。內置保留名稱 filterObject
作為集合元素的操作名來進行評估過濾。
樣例:
// 入參為Collection<String> ids 測試數據 ["Felordcn","felord","jetty"]
// 過濾掉 felord jetty 為 Felordcn
@PreFilter(value = "filterObject.startsWith('F')",filterTarget = "ids")
// 如果 當前用戶持有 ROLE_AD 角色 參數都符合 否則 過濾掉不是 f 開頭的
// DEMO 用戶不持有 ROLE_AD 角色 故而 集合只剩下 felord
@PreFilter("hasRole('AD') or filterObject.startsWith('f')")
4.4 @PostFilter
和@PreFilter
不同的是, 基於返回值相關的表達式,對返回值進行過濾。分頁慎用!該過程發生接口進行數據返回之前。 相關測試與 @PreFilter
相似,參見文末提供的 DEMO。
5. 使用 securedEnabled
如果你在 @EnableGlobalMethodSecurity
設置 securedEnabled
為 true
,就開啟了角色注解 @Secured
,該注解功能要簡單的多,默認情況下只能基於角色(默認需要帶前綴 ROLE_
)集合來進行訪問控制決策。
該注解的機制是只要其聲明的角色集合(value
)中包含當前用戶持有的任一角色就可以訪問。也就是 用戶的角色集合和 @Secured
注解的角色集合要存在非空的交集。不支持使用 SpEL 表達式進行決策。
6. 使用 jsr250Enabled
啟用 JSR-250 安全控制注解,這屬於 JavaEE 的安全規范(現為 jakarta 項目)。一共有五個安全注解。如果你在 @EnableGlobalMethodSecurity
設置 jsr250Enabled
為 true
,就開啟了 JavaEE 安全注解中的以下三個:
-
@DenyAll 拒絕所有的訪問
-
@PermitAll 同意所有的訪問
-
@RolesAllowed 用法和 5. 中的
@Secured
一樣。
7. 總結
今天講解了 Spring Security 另一種基於注解的靜態配置。相比較基於 javaConfig
的方式要靈活一些、粒度更細、基於 SpEL 表達式可以實現更加強大的功能。但是這兩種的方式還是基於編程的靜態方式,具有一定的局限性。更加靈活的方式應該是動態來處理用戶的角色和資源的映射關系,這是以后我們將要解決的問題。本次的 DEMO 可通過 關注微信公眾號:Felordcn 回復 ss09 獲取。
參考資料
Spring Security 實戰干貨: https://www.felord.cn/categories/spring-security/
[2]基於配置的接口角色訪問控制: https://www.felord.cn/spring-security-javaconfig-rbac.html
[3]上一文: https://www.felord.cn/spring-security-javaconfig-rbac.html