1. 前言
歡迎閱讀 Spring Security 實戰乾貨 系列文章 。在上一篇 基於配置的介面角色訪問控制 我們講解了如何通過 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
介面的表示式,也就是我們在上一文的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 獲取。
關注公眾號:Felordcn 獲取更多資訊