Spring Security 實戰乾貨:基於註解的介面角色訪問控制

碼農小胖哥發表於2019-11-20

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 原始碼中提供了 prePostEnabledsecuredEnabledjsr250Enabled 三種方式。當你開啟全域性基於註解的方法安全功能時,也就是使用 @EnableGlobalMethodSecurity 註解時我們需要選擇使用這三種的一種或者其中幾種。我們接下來將分別介紹它們。

4. 使用 prePostEnabled

如果你在 @EnableGlobalMethodSecurity 設定 prePostEnabledtrue ,則開啟了基於表示式的方法安全控制。通過表示式運算結果的布林值來決定是否可以訪問(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 設定 securedEnabledtrue ,就開啟了角色註解 @Secured ,該註解功能要簡單的多,預設情況下只能基於角色(預設需要帶字首 ROLE_)集合來進行訪問控制決策。

該註解的機制是隻要其宣告的角色集合(value)中包含當前使用者持有的任一角色就可以訪問。也就是 使用者的角色集合和 @Secured 註解的角色集合要存在非空的交集。 不支援使用 SpEL 表示式進行決策。

6. 使用 jsr250Enabled

啟用 JSR-250 安全控制註解,這屬於 JavaEE 的安全規範(現為 jakarta 專案)。一共有五個安全註解。如果你在 @EnableGlobalMethodSecurity 設定 jsr250Enabledtrue ,就開啟了 JavaEE 安全註解中的以下三個:

  • @DenyAll 拒絕所有的訪問
  • @PermitAll 同意所有的訪問
  • @RolesAllowed 用法和 5. 中的 @Secured 一樣。

7. 總結

今天講解了 Spring Security 另一種基於註解的靜態配置。相比較基於 javaConfig 的方式要靈活一些、粒度更細、基於 SpEL 表示式可以實現更加強大的功能。但是這兩種的方式還是基於程式設計的靜態方式,具有一定的侷限性。更加靈活的方式應該是動態來處理使用者的角色和資源的對映關係,這是以後我們將要解決的問題。
本次的 DEMO 可通過 關注微信公眾號:Felordcn 回覆 ss09 獲取。

關注公眾號:Felordcn 獲取更多資訊

個人部落格:https://felord.cn

相關文章