授權 HttpServletRequests

Spring Security 允許您在請求層級建立您的授權模型。例如,使用 Spring Security,您可以宣告 /admin 下的所有頁面需要一個權限,而所有其他頁面僅需驗證。

預設情況下,Spring Security 要求每個請求都必須經過驗證。也就是說,每當您使用 HttpSecurity 實例時,都必須宣告您的授權規則。

每當您有 HttpSecurity 實例時,您至少應該執行

使用 authorizeHttpRequests
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize(anyRequest, authenticated)
    }
}
<http>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

這會告知 Spring Security,您應用程式中的任何端點都要求安全上下文至少經過驗證才能允許存取。

在許多情況下,您的授權規則會比這更複雜,因此請考慮以下使用案例

了解請求授權元件如何運作

本節以Servlet 架構與實作為基礎,深入探討在基於 Servlet 的應用程式中,請求層級的授權如何運作。
authorizationfilter
圖 1. 授權 HttpServletRequest

AuthorizationFilter 預設為最後一個

預設情況下,AuthorizationFilterSpring Security 篩選器鏈中是最後一個。這表示 Spring Security 的驗證篩選器漏洞保護和其他篩選器整合不需要授權。如果您在 AuthorizationFilter 之前新增自己的篩選器,它們也不需要授權;否則,它們將需要授權。

當您新增 Spring MVC 端點時,這通常變得重要。因為它們由 DispatcherServlet 執行,而這發生在 AuthorizationFilter 之後,因此您的端點需要包含在 authorizeHttpRequests 中才能被允許

所有分派都已授權

AuthorizationFilter 不僅在每個請求上執行,而且在每個分派上都執行。這表示 REQUEST 分派需要授權,FORWARDERRORINCLUDE 也需要。

例如,Spring MVC 可以將請求 FORWARD 到視圖解析器,以呈現 Thymeleaf 範本,如下所示

範例轉發 Spring MVC 控制器
  • Java

  • Kotlin

@Controller
public class MyController {
    @GetMapping("/endpoint")
    public String endpoint() {
        return "endpoint";
    }
}
@Controller
class MyController {
    @GetMapping("/endpoint")
    fun endpoint(): String {
        return "endpoint"
    }
}

在這種情況下,授權會發生兩次;一次是授權 /endpoint,另一次是轉發到 Thymeleaf 以呈現 "endpoint" 範本。

因此,您可能會想要允許所有 FORWARD 分派

此原則的另一個範例是 Spring Boot 如何處理錯誤。如果容器捕獲到例外,例如以下情況

範例錯誤 Spring MVC 控制器
  • Java

  • Kotlin

@Controller
public class MyController {
    @GetMapping("/endpoint")
    public String endpoint() {
        throw new UnsupportedOperationException("unsupported");
    }
}
@Controller
class MyController {
    @GetMapping("/endpoint")
    fun endpoint(): String {
        throw UnsupportedOperationException("unsupported")
    }
}

那麼 Boot 將其分派到 ERROR 分派。

在這種情況下,授權也會發生兩次;一次是授權 /endpoint,另一次是分派錯誤。

因此,您可能會想要允許所有 ERROR 分派

Authentication 查詢被延遲

當請求始終允許或始終拒絕時,這對於 authorizeHttpRequests 來說很重要。在這些情況下,不會查詢Authentication,從而加快請求速度。

授權端點

您可以設定 Spring Security,透過依優先順序新增更多規則來擁有不同的規則。

如果您想要要求只有具有 USER 權限的終端使用者才能存取 /endpoint,則可以執行

授權端點
  • Java

  • Kotlin

  • Xml

@Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
	    .requestMatchers("/endpoint").hasAuthority("USER")
            .anyRequest().authenticated()
        )
        // ...

    return http.build();
}
@Bean
fun web(http: HttpSecurity): SecurityFilterChain {
    http {
        authorizeHttpRequests {
            authorize("/endpoint", hasAuthority("USER"))
            authorize(anyRequest, authenticated)
        }
    }

    return http.build()
}
<http>
    <intercept-url pattern="/endpoint" access="hasAuthority('USER')"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

如您所見,宣告可以分解為模式/規則對。

AuthorizationFilter 依列出的順序處理這些配對,僅將第一個比對項套用至請求。這表示即使 /** 也會比對 /endpoint,但上述規則並不是問題。閱讀上述規則的方式是「如果請求是 /endpoint,則需要 USER 權限;否則,僅需要驗證」。

Spring Security 支援多種模式和多種規則;您也可以透過程式設計方式建立自己的模式和規則。

授權後,您可以使用Security 的測試支援以以下方式進行測試

測試端點授權
  • Java

@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
    this.mvc.perform(get("/endpoint"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
    this.mvc.perform(get("/endpoint"))
        .andExpect(status().isForbidden());
}

@Test
void anyWhenUnauthenticatedThenUnauthorized() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isUnauthorized());
}

比對請求

在上方,您已經看到了兩種比對請求的方式

您看到的第一種方式是最簡單的,即比對任何請求。

第二種方式是依 URI 模式比對。Spring Security 支援兩種用於 URI 模式比對的語言:Ant(如上所示)和正規表示式

使用 Ant 比對

Ant 是 Spring Security 用於比對請求的預設語言。

您可以使用它來比對單個端點或目錄,甚至可以擷取佔位符供稍後使用。您也可以精簡它以比對一組特定的 HTTP 方法。

假設您想要比對 /resource 目錄下的所有端點,而不是想要比對 /endpoint 端點。在這種情況下,您可以執行類似以下的操作

使用 Ant 比對
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/resource/**").hasAuthority("USER")
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize("/resource/**", hasAuthority("USER"))
        authorize(anyRequest, authenticated)
    }
}
<http>
    <intercept-url pattern="/resource/**" access="hasAuthority('USER')"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

閱讀此內容的方式是「如果請求是 /resource 或某些子目錄,則需要 USER 權限;否則,僅需要驗證」

您也可以從請求中擷取路徑值,如下所示

授權和擷取
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/resource/{name}").access(new WebExpressionAuthorizationManager("#name == authentication.name"))
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize("/resource/{name}", WebExpressionAuthorizationManager("#name == authentication.name"))
        authorize(anyRequest, authenticated)
    }
}
<http>
    <intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

授權後,您可以使用Security 的測試支援以以下方式進行測試

測試目錄授權
  • Java

@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
    this.mvc.perform(get("/endpoint/jon"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
    this.mvc.perform(get("/endpoint/jon"))
        .andExpect(status().isForbidden());
}

@Test
void anyWhenUnauthenticatedThenUnauthorized() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isUnauthorized());
}
Spring Security 僅比對路徑。如果您想要比對查詢參數,則需要自訂請求比對器。

使用正規表示式比對

Spring Security 支援針對正規表示式比對請求。如果您想要套用比 ** 對子目錄更嚴格的比對條件,這會很有用。

例如,考慮一個包含使用者名稱的路徑,以及所有使用者名稱都必須是字母數字的規則。您可以使用 RegexRequestMatcher 來遵循此規則,如下所示

使用 Regex 比對
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+")).hasAuthority("USER")
        .anyRequest().denyAll()
    )
http {
    authorizeHttpRequests {
        authorize(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+"), hasAuthority("USER"))
        authorize(anyRequest, denyAll)
    }
}
<http>
    <intercept-url request-matcher="regex" pattern="/resource/[A-Za-z0-9]+" access="hasAuthority('USER')"/>
    <intercept-url pattern="/**" access="denyAll"/>
</http>

依 Http 方法比對

您也可以依 HTTP 方法比對規則。當依授予的權限進行授權時,例如被授予 readwrite 權限時,這會非常方便。

若要要求所有 GET 都具有 read 權限,而所有 POST 都具有 write 權限,您可以執行類似以下的操作

依 HTTP 方法比對
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(HttpMethod.GET).hasAuthority("read")
        .requestMatchers(HttpMethod.POST).hasAuthority("write")
        .anyRequest().denyAll()
    )
http {
    authorizeHttpRequests {
        authorize(HttpMethod.GET, hasAuthority("read"))
        authorize(HttpMethod.POST, hasAuthority("write"))
        authorize(anyRequest, denyAll)
    }
}
<http>
    <intercept-url http-method="GET" pattern="/**" access="hasAuthority('read')"/>
    <intercept-url http-method="POST" pattern="/**" access="hasAuthority('write')"/>
    <intercept-url pattern="/**" access="denyAll"/>
</http>

這些授權規則應讀作:「如果請求是 GET,則需要 read 權限;否則,如果請求是 POST,則需要 write 權限;否則,拒絕請求」

預設情況下拒絕請求是一種健全的安全實務,因為它將規則集變成允許清單。

授權後,您可以使用Security 的測試支援以以下方式進行測試

測試 Http 方法授權
  • Java

@WithMockUser(authorities="read")
@Test
void getWhenReadAuthorityThenAuthorized() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void getWhenNoReadAuthorityThenForbidden() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isForbidden());
}

@WithMockUser(authorities="write")
@Test
void postWhenWriteAuthorityThenAuthorized() {
    this.mvc.perform(post("/any").with(csrf()))
        .andExpect(status().isOk());
}

@WithMockUser(authorities="read")
@Test
void postWhenNoWriteAuthorityThenForbidden() {
    this.mvc.perform(get("/any").with(csrf()))
        .andExpect(status().isForbidden());
}

依分派器類型比對

XML 目前不支援此功能

如先前所述,Spring Security 預設授權所有分派器類型。即使在 REQUEST 分派上建立的安全上下文會延續到後續分派,但細微的不符之處有時可能會導致意外的 AccessDeniedException

為了處理這個問題,您可以將 Spring Security Java 組態設定為允許分派器類型,例如 FORWARDERROR,如下所示

範例 1. 依分派器類型比對
Java
http
    .authorizeHttpRequests((authorize) -> authorize
        .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
        .requestMatchers("/endpoint").permitAll()
        .anyRequest().denyAll()
    )
Kotlin
http {
    authorizeHttpRequests {
        authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll)
        authorize(DispatcherTypeRequestMatcher(DispatcherType.ERROR), permitAll)
        authorize("/endpoint", permitAll)
        authorize(anyRequest, denyAll)
    }
}

使用 MvcRequestMatcher

一般來說,您可以使用如上所示的 requestMatchers(String)

但是,如果您將 Spring MVC 對應到不同的 servlet 路徑,則需要在您的安全組態中考慮到這一點。

例如,如果 Spring MVC 對應到 /spring-mvc 而不是 /(預設值),則您可能有一個端點,例如 /spring-mvc/my/controller,您想要授權。

您需要使用 MvcRequestMatcher 在您的組態中分割 servlet 路徑和控制器路徑,如下所示

範例 2. 依 MvcRequestMatcher 比對
Java
@Bean
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
	return new MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");
}

@Bean
SecurityFilterChain appEndpoints(HttpSecurity http, MvcRequestMatcher.Builder mvc) {
	http
        .authorizeHttpRequests((authorize) -> authorize
            .requestMatchers(mvc.pattern("/my/controller/**")).hasAuthority("controller")
            .anyRequest().authenticated()
        );

	return http.build();
}
Kotlin
@Bean
fun mvc(introspector: HandlerMappingIntrospector): MvcRequestMatcher.Builder =
    MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");

@Bean
fun appEndpoints(http: HttpSecurity, mvc: MvcRequestMatcher.Builder): SecurityFilterChain =
    http {
        authorizeHttpRequests {
            authorize(mvc.pattern("/my/controller/**"), hasAuthority("controller"))
            authorize(anyRequest, authenticated)
        }
    }
Xml
<http>
    <intercept-url servlet-path="/spring-mvc" pattern="/my/controller/**" access="hasAuthority('controller')"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

至少在兩種不同的情況下可能會出現此需求

  • 如果您使用 spring.mvc.servlet.path Boot 屬性將預設路徑 (/) 變更為其他路徑

  • 如果您註冊多個 Spring MVC DispatcherServlet(因此要求其中一個不是預設路徑)

使用自訂比對器

XML 目前不支援此功能

在 Java 組態中,您可以建立自己的 RequestMatcher,並將其提供給 DSL,如下所示

範例 3. 依分派器類型授權
Java
RequestMatcher printview = (request) -> request.getParameter("print") != null;
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(printview).hasAuthority("print")
        .anyRequest().authenticated()
    )
Kotlin
val printview: RequestMatcher = { (request) -> request.getParameter("print") != null }
http {
    authorizeHttpRequests {
        authorize(printview, hasAuthority("print"))
        authorize(anyRequest, authenticated)
    }
}
由於 RequestMatcher 是一個函數介面,因此您可以將其作為 DSL 中的 lambda 提供。但是,如果您想要從請求中擷取值,則需要有一個具體的類別,因為這需要覆寫 default 方法。

授權後,您可以使用Security 的測試支援以以下方式進行測試

測試自訂授權
  • Java

@WithMockUser(authorities="print")
@Test
void printWhenPrintAuthorityThenAuthorized() {
    this.mvc.perform(get("/any?print"))
        .andExpect(status().isOk());
}

@WithMockUser
@Test
void printWhenNoPrintAuthorityThenForbidden() {
    this.mvc.perform(get("/any?print"))
        .andExpect(status().isForbidden());
}

授權請求

一旦請求被比對,您就可以使用已經看到的幾種方式來授權它,例如 permitAlldenyAllhasAuthority

作為快速摘要,以下是 DSL 中內建的授權規則

  • permitAll - 請求不需要授權,並且是公用端點;請注意,在這種情況下,永遠不會從 session 中擷取Authentication

  • denyAll - 在任何情況下都不允許請求;請注意,在這種情況下,永遠不會從 session 中擷取 Authentication

  • hasAuthority - 請求要求 GrantedAuthority 具有與給定值比對的 Authentication

  • hasRole - hasAuthority 的捷徑,它會為 ROLE_ 或任何設定為預設前綴的值加上前綴

  • hasAnyAuthority - 請求要求 Authentication 具有與任何給定值比對的 GrantedAuthority

  • hasAnyRole - hasAnyAuthority 的捷徑,它會為 ROLE_ 或任何設定為預設前綴的值加上前綴

  • access - 請求使用此自訂 AuthorizationManager 來判斷存取權

現在已經了解了模式、規則以及它們如何配對在一起,您應該能夠了解這個更複雜的範例中發生了什麼

授權請求
  • Java

import static jakarta.servlet.DispatcherType.*;

import static org.springframework.security.authorization.AuthorizationManagers.allOf;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
	http
		// ...
		.authorizeHttpRequests(authorize -> authorize                                  (1)
            .dispatcherTypeMatchers(FORWARD, ERROR).permitAll() (2)
			.requestMatchers("/static/**", "/signup", "/about").permitAll()         (3)
			.requestMatchers("/admin/**").hasRole("ADMIN")                             (4)
			.requestMatchers("/db/**").access(allOf(hasAuthority("db"), hasRole("ADMIN")))   (5)
			.anyRequest().denyAll()                                                (6)
		);

	return http.build();
}
1 指定了多個授權規則。每個規則都依宣告的順序考量。
2 允許 FORWARDERROR 分派,以允許 Spring MVC 呈現視圖和 Spring Boot 呈現錯誤
3 我們指定了多個 URL 模式,任何使用者都可以存取。具體來說,如果 URL 以 "/static/" 開頭、等於 "/signup" 或等於 "/about",則任何使用者都可以存取請求。
4 任何以 "/admin/" 開頭的 URL 都將限制為具有 "ROLE_ADMIN" 角色的使用者。您會注意到,由於我們正在調用 hasRole 方法,因此我們不需要指定 "ROLE_" 前綴。
5 任何以 "/db/" 開頭的 URL 都要求使用者同時被授予 "db" 權限和 "ROLE_ADMIN"。您會注意到,由於我們正在使用 hasRole 運算式,因此我們不需要指定 "ROLE_" 前綴。
6 任何尚未比對的 URL 都會被拒絕存取。如果您不想意外忘記更新您的授權規則,這是一個很好的策略。

使用 SpEL 表示授權

雖然建議使用具體的 AuthorizationManager,但在某些情況下,運算式是必要的,例如使用 <intercept-url> 或 JSP Taglibs。因此,本節將重點介紹這些網域中的範例。

有鑑於此,讓我們更深入地介紹 Spring Security 的 Web 安全授權 SpEL API。

Spring Security 將其所有授權欄位和方法封裝在一組根物件中。最通用的根物件稱為 SecurityExpressionRoot,它是 WebSecurityExpressionRoot 的基礎。Spring Security 在準備評估授權運算式時,會將此根物件提供給 StandardEvaluationContext

使用授權運算式欄位和方法

這提供的第一件事是為您的 SpEL 運算式增強一組授權欄位和方法。以下是常用方法的快速總覽

  • permitAll - 請求不需要授權即可調用;請注意,在這種情況下,永遠不會從 session 中擷取Authentication

  • denyAll - 在任何情況下都不允許請求;請注意,在這種情況下,永遠不會從 session 中擷取 Authentication

  • hasAuthority - 請求要求 GrantedAuthority 具有與給定值比對的 Authentication

  • hasRole - hasAuthority 的捷徑,它會為 ROLE_ 或任何設定為預設前綴的值加上前綴

  • hasAnyAuthority - 請求要求 Authentication 具有與任何給定值比對的 GrantedAuthority

  • hasAnyRole - hasAnyAuthority 的捷徑,它會為 ROLE_ 或任何設定為預設前綴的值加上前綴

  • hasPermission - 連接到您的 PermissionEvaluator 實例的 Hook,用於執行物件層級的授權

以下是常用欄位的簡要介紹

  • authentication - 與此方法調用關聯的 Authentication 實例

  • principal - 與此方法調用關聯的 Authentication#getPrincipal

現在已經了解了模式、規則以及它們如何配對在一起,您應該能夠了解這個更複雜的範例中發生了什麼

使用 SpEL 授權請求
  • Xml

<http>
    <intercept-url pattern="/static/**" access="permitAll"/> (1)
    <intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/> (2)
    <intercept-url pattern="/db/**" access="hasAuthority('db') and hasRole('ADMIN')"/> (3)
    <intercept-url pattern="/**" access="denyAll"/> (4)
</http>
1 我們指定了一個 URL 模式,任何使用者都可以存取。具體來說,如果 URL 以 "/static/" 開頭,則任何使用者都可以存取請求。
2 任何以 "/admin/" 開頭的 URL 都將限制為具有 "ROLE_ADMIN" 角色的使用者。您會注意到,由於我們正在調用 hasRole 方法,因此我們不需要指定 "ROLE_" 前綴。
3 任何以 "/db/" 開頭的 URL 都要求使用者同時被授予 "db" 權限和 "ROLE_ADMIN"。您會注意到,由於我們正在使用 hasRole 運算式,因此我們不需要指定 "ROLE_" 前綴。
4 任何尚未比對的 URL 都會被拒絕存取。如果您不想意外忘記更新您的授權規則,這是一個很好的策略。

使用路徑參數

此外,Spring Security 提供了一種發現路徑參數的機制,以便也可以在 SpEL 運算式中存取它們。

例如,您可以透過以下方式在 SpEL 運算式中存取路徑參數

使用 SpEL 路徑變數授權請求
  • Xml

<http>
    <intercept-url pattern="/resource/{name}" access="#name == authentication.name"/>
    <intercept-url pattern="/**" access="authenticated"/>
</http>

此運算式在 /resource/ 之後引用路徑變數,並要求它等於 Authentication#getName

使用授權資料庫、策略代理程式或其他服務

如果您想要設定 Spring Security 以使用單獨的服務進行授權,您可以建立自己的 AuthorizationManager,並將其與 anyRequest 比對。

首先,您的 AuthorizationManager 可能看起來像這樣

Open Policy Agent 授權管理器
  • Java

@Component
public final class OpenPolicyAgentAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        // make request to Open Policy Agent
    }
}

然後,您可以透過以下方式將其連接到 Spring Security 中

任何請求都轉到遠端服務
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> authz) throws Exception {
	http
		// ...
		.authorizeHttpRequests((authorize) -> authorize
            .anyRequest().access(authz)
		);

	return http.build();
}

偏好 permitAll 而非 ignoring

當您有靜態資源時,可能會很想設定篩選器鏈以忽略這些值。更安全的方法是使用 permitAll 允許它們,如下所示

範例 4. 允許靜態資源
Java
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/css/**").permitAll()
        .anyRequest().authenticated()
    )
Kotlin
http {
    authorizeHttpRequests {
        authorize("/css/**", permitAll)
        authorize(anyRequest, authenticated)
    }
}

它更安全,因為即使對於靜態資源,撰寫安全標頭也很重要,如果請求被忽略,Spring Security 無法執行此操作。

在過去,這會帶來效能上的權衡,因為 Spring Security 會在每個請求上查詢 session。但是,從 Spring Security 6 開始,除非授權規則要求,否則不再 ping session。由於效能影響現在已解決,Spring Security 建議至少對所有請求使用 permitAll

authorizeRequests 遷移

AuthorizationFilter 取代了 FilterSecurityInterceptor。為了保持向後相容性,FilterSecurityInterceptor 仍然是預設值。本節討論 AuthorizationFilter 的運作方式,以及如何覆寫預設組態。

AuthorizationFilterHttpServletRequest 提供授權。它作為安全篩選器之一插入到 FilterChainProxy 中。

當您宣告 SecurityFilterChain 時,可以覆寫預設值。不要使用 authorizeRequests,而是使用 authorizeHttpRequests,如下所示

使用 authorizeHttpRequests
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http) throws AuthenticationException {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated();
        )
        // ...

    return http.build();
}

這在許多方面改進了 authorizeRequests

  1. 使用簡化的 AuthorizationManager API,而非中繼資料來源、組態屬性、決策管理器和投票器。這簡化了重用和自訂。

  2. 延遲 Authentication 查找。不需要在每個請求中都查找身份驗證,而只會在授權決策需要身份驗證的請求中查找。

  3. 基於 Bean 的組態支援。

當使用 authorizeHttpRequests 而非 authorizeRequests 時,則會使用 AuthorizationFilter,而非 FilterSecurityInterceptor

遷移表達式

在可能的情況下,建議您使用型別安全的授權管理器,而非 SpEL。對於 Java 組態,WebExpressionAuthorizationManager 可用於協助遷移舊有的 SpEL。

若要使用 WebExpressionAuthorizationManager,您可以像這樣使用您嘗試遷移的表達式來建構一個。

  • Java

  • Kotlin

.requestMatchers("/test/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
.requestMatchers("/test/**").access(WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))

如果您在表達式中引用 Bean,例如這樣:@webSecurity.check(authentication, request),建議您改為直接呼叫 Bean,如下所示:

  • Java

  • Kotlin

.requestMatchers("/test/**").access((authentication, context) ->
    new AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
.requestMatchers("/test/**").access((authentication, context): AuthorizationManager<RequestAuthorizationContext> ->
    AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))

對於包含 Bean 參考以及其他表達式的複雜指令,建議您將其更改為實作 AuthorizationManager,並透過呼叫 .access(AuthorizationManager) 來引用它們。

如果您無法做到這一點,您可以配置一個帶有 Bean 解析器的 DefaultHttpSecurityExpressionHandler,並將其提供給 WebExpressionAuthorizationManager#setExpressionhandler

安全性匹配器

RequestMatcher 介面用於判斷請求是否符合給定的規則。我們使用 securityMatchers 來判斷是否應將 給定的 HttpSecurity 應用於給定的請求。同樣地,我們可以使用 requestMatchers 來判斷應將哪些授權規則應用於給定的請求。請看以下範例:

  • Java

  • Kotlin

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.securityMatcher("/api/**")                            (1)
			.authorizeHttpRequests(authorize -> authorize
				.requestMatchers("/user/**").hasRole("USER")       (2)
				.requestMatchers("/admin/**").hasRole("ADMIN")     (3)
				.anyRequest().authenticated()                      (4)
			)
			.formLogin(withDefaults());
		return http.build();
	}
}
@Configuration
@EnableWebSecurity
open class SecurityConfig {

    @Bean
    open fun web(http: HttpSecurity): SecurityFilterChain {
        http {
            securityMatcher("/api/**")                                           (1)
            authorizeHttpRequests {
                authorize("/user/**", hasRole("USER"))                           (2)
                authorize("/admin/**", hasRole("ADMIN"))                         (3)
                authorize(anyRequest, authenticated)                             (4)
            }
        }
        return http.build()
    }

}
1 配置 HttpSecurity 以僅應用於以 /api/ 開頭的 URL。
2 允許具有 USER 角色的使用者存取以 /user/ 開頭的 URL。
3 允許具有 ADMIN 角色的使用者存取以 /admin/ 開頭的 URL。
4 任何其他不符合上述規則的請求都將需要身份驗證。

securityMatcher(s)requestMatcher(s) 方法將決定哪種 RequestMatcher 實作最適合您的應用程式:如果 Spring MVC 在類別路徑中,則將使用 MvcRequestMatcher,否則將使用 AntPathRequestMatcher。您可以在此處閱讀有關 Spring MVC 整合的更多資訊。

如果您想使用特定的 RequestMatcher,只需將實作傳遞給 securityMatcher 和/或 requestMatcher 方法即可。

  • Java

  • Kotlin

import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; (1)
import static org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.securityMatcher(antMatcher("/api/**"))                              (2)
			.authorizeHttpRequests(authorize -> authorize
				.requestMatchers(antMatcher("/user/**")).hasRole("USER")         (3)
				.requestMatchers(regexMatcher("/admin/.*")).hasRole("ADMIN")     (4)
				.requestMatchers(new MyCustomRequestMatcher()).hasRole("SUPERVISOR")     (5)
				.anyRequest().authenticated()
			)
			.formLogin(withDefaults());
		return http.build();
	}
}

public class MyCustomRequestMatcher implements RequestMatcher {

    @Override
    public boolean matches(HttpServletRequest request) {
        // ...
    }
}
import org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher (1)
import org.springframework.security.web.util.matcher.RegexRequestMatcher.regexMatcher

@Configuration
@EnableWebSecurity
open class SecurityConfig {

    @Bean
    open fun web(http: HttpSecurity): SecurityFilterChain {
        http {
            securityMatcher(antMatcher("/api/**"))                               (2)
            authorizeHttpRequests {
                authorize(antMatcher("/user/**"), hasRole("USER"))               (3)
                authorize(regexMatcher("/admin/**"), hasRole("ADMIN"))           (4)
                authorize(MyCustomRequestMatcher(), hasRole("SUPERVISOR"))       (5)
                authorize(anyRequest, authenticated)
            }
        }
        return http.build()
    }

}
1 AntPathRequestMatcherRegexRequestMatcher 匯入靜態 factory 方法以建立 RequestMatcher 實例。
2 使用 AntPathRequestMatcher 配置 HttpSecurity 以僅應用於以 /api/ 開頭的 URL。
3 使用 AntPathRequestMatcher 允許具有 USER 角色的使用者存取以 /user/ 開頭的 URL。
4 使用 RegexRequestMatcher 允許具有 ADMIN 角色的使用者存取以 /admin/ 開頭的 URL。
5 使用自訂 RequestMatcher 允許符合 MyCustomRequestMatcher 的 URL 讓具有 SUPERVISOR 角色的使用者存取。

延伸閱讀

既然您已保護了應用程式的請求,請考慮保護其方法。您也可以進一步閱讀關於測試您的應用程式,或將 Spring Security 與您應用程式的其他方面整合,例如資料層追蹤和指標