跳到主要内容
版本:7.0.2

WebFlux 安全

DeepSeek V3 中英对照 WebFlux Security

Spring Security 的 WebFlux 支持依赖于 WebFilter,并且对 Spring WebFlux 和 Spring WebFlux.Fn 的工作方式相同。以下是一些演示代码的示例应用程序:

最小化 WebFlux 安全配置

以下清单展示了一个最小的 WebFlux 安全配置:

@Configuration
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {

@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}

此配置提供了表单和HTTP基本认证,设置了授权要求,访问任何页面都需要经过身份验证的用户,设置了默认登录页面和默认注销页面,配置了安全相关的HTTP头部,添加了CSRF保护等功能。

显式 WebFlux 安全配置

以下页面展示了最小化 WebFlux 安全配置的显式版本:

@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();
}
}
备注

请确保导入 org.springframework.security.config.web.server.invoke 函数以在您的类中启用 Kotlin DSL,因为 IDE 并不总是会自动导入该方法,这可能导致编译问题。

此配置明确设置了与我们最小配置相同的所有内容。从这里开始,您可以更轻松地修改默认设置。

您可以在单元测试中找到更多显式配置的示例,具体方法是搜索 config/src/test/ 目录中的 EnableWebFluxSecurity

多链支持

你可以配置多个 SecurityWebFilterChain 实例,通过 RequestMatcher 实例来分离配置。

例如,你可以为以 /api 开头的 URL 隔离配置:

@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());
}

}
  • 配置一个带有 @Order 注解的 SecurityWebFilterChain,以指定 Spring Security 应优先考虑哪个 SecurityWebFilterChain

  • 使用 PathPatternParserServerWebExchangeMatcher 声明此 SecurityWebFilterChain 仅适用于以 /api/ 开头的 URL 路径

  • 指定用于 /api/** 端点的认证机制

  • 创建另一个优先级较低的 SecurityWebFilterChain 实例以匹配所有其他 URL

  • 指定用于应用程序其余部分的认证机制

Spring Security 会为每个请求选择一个 SecurityWebFilterChain @Bean。它会按照 securityMatcher 的定义顺序来匹配请求。

在这种情况下,这意味着,如果 URL 路径以 /api 开头,Spring Security 将使用 apiHttpSecurity。如果 URL 不以 /api 开头,Spring Security 将默认使用 webHttpSecurity,它有一个隐含的 securityMatcher,可以匹配任何请求。

模块化 ServerHttpSecurity 配置

许多用户倾向于将 Spring Security 配置集中管理,并选择在 SecurityWebFilterChain Bean 声明中进行配置。然而,有时用户可能希望将配置模块化。这可以通过以下方式实现:

Customizer<ServerHttpSecurity> Beans

如果您希望模块化您的安全配置,可以将逻辑放在一个 Customizer<ServerHttpSecurity> Bean 中。例如,以下配置将确保所有 ServerHttpSecurity 实例都被配置为:

@Bean
Customizer<ServerHttpSecurity> httpSecurityCustomizer() {
return (http) -> http
.headers((headers) -> headers
.contentSecurityPolicy((csp) -> csp
1
.policyDirectives("object-src 'none'")
)
)
2
.redirectToHttps(Customizer.withDefaults());
}

顶级 ServerHttpSecurity 自定义器 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'

@Bean
Customizer<ServerHttpSecurity.HeaderSpec> headersSecurity() {
return (headers) -> headers
.contentSecurityPolicy((csp) -> csp
1
.policyDirectives("object-src 'none'")
);
}

自定义器 Bean 的排序

首先,每个 Customizer<HttpSecurity> Bean 会通过 ObjectProvider#orderedStream() 方法按顺序应用。这意味着,如果存在多个 Customizer<HttpSecurity> Bean,可以在 Bean 定义上添加 @Order 注解来控制它们的执行顺序。

接下来,查找每个顶级 HttpSecurity 自定义器 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 提供的自定义配置。

以下是一个说明排序方式的示例:

@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();
}
  • 首先应用所有 Customizer<HttpSecurity> 实例。adminAuthorization Bean 的 @Order 值最高,因此最先被应用。如果 Customizer<HttpSecurity> Bean 上没有 @Order 注解,或者 @Order 注解的值相同,那么 Customizer<HttpSecurity> 实例的应用顺序是未定义的。

  • 接下来应用 userAuthorization,因为它是一个 Customizer<HttpSecurity> 实例。

  • Customizer 类型的应用顺序是未定义的。在此示例中,contentSecurityPolicycontentTypeOptionshttpsRedirect 的顺序是未定义的。如果给 contentTypeOptions 添加了 @Order(Ordered.HIGHEST_PRECEDENCE),那么我们可以知道 contentTypeOptionscontentSecurityPolicy 之前(它们是同一类型),但我们无法确定 httpsRedirect 是在 Customizer<HeadersConfigurer<HttpSecurity>> Bean 之前还是之后。

  • 在所有 Customizer Bean 应用完毕后,HttpSecurity 会作为一个 Bean 被传入。