授權 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 位於最後

預設情況下,AuthorizationFilter 位於Spring Security 過濾器鏈的最後。這意味著 Spring Security 的認證過濾器攻擊防護和其他過濾器整合不需要授權。如果你在 AuthorizationFilter 之前新增自己的過濾器,它們也不需要授權;否則,它們將需要授權。

通常,在你新增Spring MVC端點時,這一點變得很重要。因為它們由DispatcherServlet執行,而 DispatcherServletAuthorizationFilter 之後執行,所以你的端點需要包含在 authorizeHttpRequests才能被允許訪問。

所有轉發都經過授權

AuthorizationFilter 不僅在每個請求上執行,而且在每個轉發 (dispatch) 上執行。這意味著 REQUEST 轉發需要授權,`FORWARD`、`ERROR` 和 `INCLUDE` 也需要。

例如,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 具有不同的規則。

如果你想要求 /endpoint 只能由具有 USER 許可權的終端使用者訪問,那麼你可以這樣做

授權端點
  • 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 支援多種模式和多種規則;你也可以以程式設計方式建立自己的模式和規則。

一旦授權,你可以使用Spring 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 方法集合。

假設你不想匹配 /endpoint 端點,而是想匹配 /resource 目錄下的所有端點。在這種情況下,你可以執行以下操作

使用 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>

一旦授權,你可以使用Spring 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 許可權;否則,拒絕請求”

預設拒絕請求是一種健康的安全性實踐,因為它將規則集變成了允許列表。

一旦授權,你可以使用Spring 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 等轉發型別,如下所示

按轉發型別匹配
  • Java

  • Kotlin

http
    .authorizeHttpRequests((authorize) -> authorize
        .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
        .requestMatchers("/endpoint").permitAll()
        .anyRequest().denyAll()
    )
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 路徑和 controller 路徑,如下所示

使用 MvcRequestMatcher 匹配
  • Java

  • Kotlin

  • Xml

@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();
}
@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)
        }
    }
<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,如下所示

按轉發型別授權
  • Java

  • Kotlin

RequestMatcher printview = (request) -> request.getParameter("print") != null;
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers(printview).hasAuthority("print")
        .anyRequest().authenticated()
    )
val printview: RequestMatcher = { (request) -> request.getParameter("print") != null }
http {
    authorizeHttpRequests {
        authorize(printview, hasAuthority("print"))
        authorize(anyRequest, authenticated)
    }
}
因為RequestMatcher是一個函式式介面,你可以在 DSL 中將其作為 lambda 提供。但是,如果你想從請求中提取值,則需要一個具體類,因為這需要覆蓋 default 方法。

一旦授權,你可以使用Spring 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 - 請求不需要授權,是公共端點;注意,在這種情況下,Authentication 不會從會話中檢索

  • denyAll - 在任何情況下都不允許該請求;注意,在這種情況下,Authentication 不會從會話中檢索

  • hasAuthority - 請求要求 Authentication 具有與給定值匹配的GrantedAuthority

  • 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 Security Authorization SpEL API。

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

使用授權表示式欄位和方法

這提供的第一件事是為你的 SpEL 表示式提供了一組增強的授權欄位和方法。以下是常用方法的快速概覽

  • permitAll - 呼叫該請求不需要授權;注意,在這種情況下,Authentication 不會從會話中檢索

  • denyAll - 在任何情況下都不允許該請求;注意,在這種情況下,Authentication 不會從會話中檢索

  • hasAuthority - 請求要求 Authentication 具有與給定值匹配的GrantedAuthority

  • hasRole - hasAuthority 的快捷方式,它會新增 ROLE_ 字首或配置為預設字首的任何內容

  • hasAnyAuthority - 請求要求 Authentication 具有與給定值中的任何一個匹配的 GrantedAuthority

  • hasAnyRole - hasAnyAuthority 的快捷方式,它會新增 ROLE_ 字首或配置為預設字首的任何內容

  • hasPermission - 用於執行物件級別授權的 PermissionEvaluator 例項的鉤子

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

  • 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 允許它們,如下所示

允許靜態資源
  • Java

  • Kotlin

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/css/**").permitAll()
        .anyRequest().authenticated()
    )
http {
    authorizeHttpRequests {
        authorize("/css/**", permitAll)
        authorize(anyRequest, authenticated)
    }
}

這更安全,因為即使對於靜態資源,編寫安全頭也很重要,如果請求被忽略,Spring Security 將無法做到這一點。

過去,這會帶來效能折衷,因為 Spring Security 在每個請求上都會查詢會話。然而,自 Spring Security 6 起,除非授權規則要求,否則不再查詢會話。由於效能影響現已解決,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("/api/user/**").hasRole("USER")   (2)
				.requestMatchers("/api/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("/api/user/**", hasRole("USER"))                       (2)
                authorize("/api/admin/**", hasRole("ADMIN"))                     (3)
                authorize(anyRequest, authenticated)                             (4)
            }
        }
        return http.build()
    }

}
1 配置 HttpSecurity 僅應用於以 /api/ 開頭的 URL
2 允許具有 USER 角色的使用者訪問以 /api/user/ 開頭的 URL
3 允許具有 ADMIN 角色的使用者訪問以 /api/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("/api/user/**")).hasRole("USER")     (3)
				.requestMatchers(regexMatcher("/api/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("/api/user/**"), hasRole("USER"))           (3)
                authorize(regexMatcher("/api/admin/**"), hasRole("ADMIN"))       (4)
                authorize(MyCustomRequestMatcher(), hasRole("SUPERVISOR"))       (5)
                authorize(anyRequest, authenticated)
            }
        }
        return http.build()
    }

}
1 匯入 AntPathRequestMatcherRegexRequestMatcher 中的靜態工廠方法來建立 RequestMatcher 例項。
2 配置 HttpSecurity 以僅應用於以 /api/ 開頭的 URL,使用 AntPathRequestMatcher
3 允許具有 USER 角色的使用者訪問以 /api/user/ 開頭的 URL,使用 AntPathRequestMatcher
4 允許具有 ADMIN 角色的使用者訪問以 /api/admin/ 開頭的 URL,使用 RegexRequestMatcher
5 允許具有 SUPERVISOR 角色的使用者訪問與 MyCustomRequestMatcher 匹配的 URL,使用自定義的 RequestMatcher

延伸閱讀

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