WebFlux 安全
Spring Security 的 WebFlux 支援依賴於一個 WebFilter,並且對於 Spring WebFlux 和 Spring WebFlux.Fn 都同樣有效。以下是一些演示程式碼的示例應用程式
-
Hello WebFlux hellowebflux
-
Hello WebFlux.Fn hellowebfluxfn
-
Hello WebFlux 方法 hellowebflux-method
最小 WebFlux 安全配置
以下列表顯示了一個最小的 WebFlux 安全配置
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}
@Configuration
@EnableWebFluxSecurity
class HelloWebfluxSecurityConfig {
@Bean
fun userDetailsService(): ReactiveUserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build()
return MapReactiveUserDetailsService(userDetails)
}
}
此配置提供表單和 HTTP 基本認證,設定授權以要求認證使用者訪問任何頁面,設定預設登入頁面和預設登出頁面,設定安全相關的 HTTP 頭部,新增 CSRF 保護等等。
顯式 WebFlux 安全配置
以下頁面顯示了最小 WebFlux 安全配置的顯式版本
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.httpBasic(withDefaults())
.formLogin(withDefaults());
return http.build();
}
}
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
class HelloWebfluxSecurityConfig {
@Bean
fun userDetailsService(): ReactiveUserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build()
return MapReactiveUserDetailsService(userDetails)
}
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
formLogin { }
httpBasic { }
}
}
}
請確保匯入 org.springframework.security.config.web.server.invoke 函式以在您的類中啟用 Kotlin DSL,因為 IDE 不總是會自動匯入該方法,這會導致編譯問題。 |
此配置顯式設定了與最小配置相同的所有內容。從這裡,您可以更容易地更改預設值。
您可以透過在 config/src/test/ 目錄中搜索 EnableWebFluxSecurity 來找到更多顯式配置的示例。
多鏈支援
您可以配置多個 SecurityWebFilterChain 例項,透過 RequestMatcher 例項來分離配置。
例如,您可以為以 /api 開頭的 URL 隔離配置
-
Java
-
Kotlin
@Configuration
@EnableWebFluxSecurity
static class MultiSecurityHttpConfig {
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
@Bean
SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http) {
http
.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/**")) (2)
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt); (3)
return http.build();
}
@Bean
SecurityWebFilterChain webHttpSecurity(ServerHttpSecurity http) { (4)
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.httpBasic(withDefaults()); (5)
return http.build();
}
@Bean
ReactiveUserDetailsService userDetailsService() {
return new MapReactiveUserDetailsService(
PasswordEncodedUser.user(), PasswordEncodedUser.admin());
}
}
import org.springframework.security.config.web.server.invoke
@Configuration
@EnableWebFluxSecurity
open class MultiSecurityHttpConfig {
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
@Bean
open fun apiHttpSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
securityMatcher(PathPatternParserServerWebExchangeMatcher("/api/**")) (2)
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2ResourceServer {
jwt { } (3)
}
}
}
@Bean
open fun webHttpSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { (4)
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
httpBasic { } (5)
}
}
@Bean
open fun userDetailsService(): ReactiveUserDetailsService {
return MapReactiveUserDetailsService(
PasswordEncodedUser.user(), PasswordEncodedUser.admin()
)
}
}
| 1 | 配置一個帶有 @Order 的 SecurityWebFilterChain,以指定 Spring Security 應該首先考慮哪個 SecurityWebFilterChain |
| 2 | 使用 PathPatternParserServerWebExchangeMatcher 來宣告此 SecurityWebFilterChain 將僅適用於以 /api/ 開頭的 URL 路徑 |
| 3 | 指定將用於 /api/** 端點的認證機制 |
| 4 | 建立另一個優先順序較低的 SecurityWebFilterChain 例項以匹配所有其他 URL |
| 5 | 指定將用於應用程式其餘部分的認證機制 |
Spring Security 為每個請求選擇一個 SecurityWebFilterChain @Bean。它按照 securityMatcher 的定義順序匹配請求。
在這種情況下,這意味著如果 URL 路徑以 /api 開頭,Spring Security 使用 apiHttpSecurity。如果 URL 不以 /api 開頭,Spring Security 預設使用 webHttpSecurity,它有一個隱式的 securityMatcher,匹配任何請求。
模組化 ServerHttpSecurity 配置
許多使用者更喜歡將他們的 Spring Security 配置集中在一個地方,並選擇在 SecurityWebFilterChain Bean 宣告中進行配置。然而,有時使用者可能希望將配置模組化。這可以透過以下方式完成
Customizer<ServerHttpSecurity> Bean
如果您想模組化您的安全配置,可以將邏輯放在 Customizer<ServerHttpSecurity> Bean 中。例如,以下配置將確保所有 ServerHttpSecurity 例項都配置為
-
Java
-
Kotlin
@Bean
Customizer<ServerHttpSecurity> httpSecurityCustomizer() {
return (http) -> http
.headers((headers) -> headers
.contentSecurityPolicy((csp) -> csp
(1)
.policyDirectives("object-src 'none'")
)
)
(2)
.redirectToHttps(Customizer.withDefaults());
}
@Bean
fun httpSecurityCustomizer(): Customizer<ServerHttpSecurity> {
return Customizer { http -> http
.headers { headers -> headers
.contentSecurityPolicy { csp -> csp
(1)
.policyDirectives("object-src 'none'")
}
}
(2)
.redirectToHttps(Customizer.withDefaults())
}
}
| 1 | 將 內容安全策略 設定為 object-src 'none' |
| 2 | 將任何請求重定向到 https |
頂級 ServerHttpSecurity Customizer Bean
如果您更喜歡進一步模組化您的安全配置,Spring Security 將自動應用任何頂級 HttpSecurity Customizer Bean。
頂級 HttpSecurity Customizer 型別可以概括為任何與 public HttpSecurity.*(Customizer<T>) 匹配的 Customizer<T>。這表示任何作為 HttpSecurity 上公共方法的單個引數的 Customizer<T>。
一些示例可以幫助澄清。如果 Customizer<ContentTypeOptionsConfig> 作為 Bean 釋出,它將不會自動應用,因為它是一個引數 HeadersConfigurer.contentTypeOptions(Customizer),而這不是在 HttpSecurity 上定義的方法。然而,如果 Customizer<HeadersConfigurer<HttpSecurity>> 作為 Bean 釋出,它將自動應用,因為它是一個引數 HttpSecurity.headers(Customizer)。
例如,以下配置將確保 內容安全策略 設定為 object-src 'none'
-
Java
-
Kotlin
@Bean
Customizer<ServerHttpSecurity.HeaderSpec> headersSecurity() {
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
(1)
.policyDirectives("object-src 'none'")
);
}
@Bean
fun headersSecurity(): Customizer<ServerHttpSecurity.HeaderSpec> {
return Customizer { headers -> headers
.contentSecurityPolicy { csp -> csp
(1)
.policyDirectives("object-src 'none'")
}
}
}
Customizer Bean 排序
首先,每個 Customizer<HttpSecurity> Bean 都使用 ObjectProvider#orderedStream() 應用。這意味著如果存在多個 Customizer<HttpSecurity> Bean,可以在 Bean 定義中新增 @Order 註解來控制排序。
接下來,查詢每個 頂級 HttpSecurity Customizer Bean 型別,並使用 ObjectProvider#orderedStream() 應用每個型別。如果存在兩個 Customizer<HeadersConfigurer<HttpSecurity>> Bean 和兩個 Customizer<HttpsRedirectConfigurer<HttpSecurity>> 例項,則呼叫每個 Customizer 型別的順序是未定義的。但是,每個 Customizer<HttpsRedirectConfigurer<HttpSecurity>> 例項的順序由 ObjectProvider#orderedStream() 定義,並且可以使用 Bean 定義上的 @Order 進行控制。
最後,HttpSecurity Bean 作為 Bean 注入。所有 Customizer 例項都在 HttpSecurity Bean 建立之前應用。這允許覆蓋 Customizer Bean 提供的自定義。
您可以在下面找到一個說明排序的示例
-
Java
-
Kotlin
@Bean (4)
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
http
.authorizeExchange((exchange) -> exchange
.anyExchange().authenticated()
);
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) (2)
Customizer<ServerHttpSecurity> userAuthorization() {
return (http) -> http
.authorizeExchange((exchange) -> exchange
.pathMatchers("/users/**").hasRole("USER")
);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
Customizer<ServerHttpSecurity> adminAuthorization() {
return (http) -> http
.authorizeExchange((exchange) -> exchange
.pathMatchers("/admins/**").hasRole("ADMIN")
);
}
(3)
@Bean
Customizer<ServerHttpSecurity.HeaderSpec> contentSecurityPolicy() {
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
.policyDirectives("object-src 'none'")
);
}
@Bean
Customizer<ServerHttpSecurity.HeaderSpec> contentTypeOptions() {
return (headers) -> headers
.contentTypeOptions(Customizer.withDefaults());
}
@Bean
Customizer<ServerHttpSecurity.HttpsRedirectSpec> httpsRedirect() {
return Customizer.withDefaults();
}
@Bean (4)
fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
http
.authorizeExchange({ exchanges -> exchanges
.anyExchange().authenticated()
})
return http.build()
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE) (2)
fun userAuthorization(): Customizer<ServerHttpSecurity> {
return Customizer { http -> http
.authorizeExchange { exchanges -> exchanges
.pathMatchers("/users/**").hasRole("USER")
}
}
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) (1)
fun adminAuthorization(): Customizer<ServerHttpSecurity> {
return ThrowingCustomizer { http -> http
.authorizeExchange { exchanges -> exchanges
.pathMatchers("/admins/**").hasRole("ADMIN")
}
}
}
(3)
@Bean
fun contentSecurityPolicy(): Customizer<ServerHttpSecurity.HeaderSpec> {
return Customizer { headers -> headers
.contentSecurityPolicy { csp -> csp
.policyDirectives("object-src 'none'")
}
}
}
@Bean
fun contentTypeOptions(): Customizer<ServerHttpSecurity.HeaderSpec> {
return Customizer { headers -> headers
.contentTypeOptions(Customizer.withDefaults())
}
}
@Bean
fun httpsRedirect(): Customizer<ServerHttpSecurity.HttpsRedirectSpec> {
return Customizer.withDefaults()
}
| 1 | 首先應用所有 Customizer<HttpSecurity> 例項。adminAuthorization Bean 具有最高的 @Order,因此它首先應用。如果 Customizer<HttpSecurity> Bean 上沒有 @Order 註解或 @Order 註解具有相同的值,則 Customizer<HttpSecurity> 例項的應用順序是未定義的。 |
| 2 | userAuthorization 接下來應用,因為它是一個 Customizer<HttpSecurity> 例項 |
| 3 | Customizer 型別的順序是未定義的。在此示例中,contentSecurityPolicy、contentTypeOptions 和 httpsRedirect 的順序是未定義的。如果將 @Order(Ordered.HIGHEST_PRECEDENCE) 新增到 contentTypeOptions,那麼我們將知道 contentTypeOptions 在 contentSecurityPolicy 之前(它們是相同的型別),但我們不知道 httpsRedirect 是在 Customizer<HeadersConfigurer<HttpSecurity>> Bean 之前還是之後。 |
| 4 | 應用所有 Customizer Bean 後,將 HttpSecurity 作為 Bean 傳入。 |