此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Security 6.5.3! |
WebFlux 安全
Spring Security 的 WebFlux 支持依赖于WebFilter
并且对 Spring WebFlux 和 Spring WebFlux.Fn 的工作原理相同。
一些示例应用程序演示了代码:
-
你好 WebFlux hellowebflux
-
你好 WebFlux.Fn 你好webfluxfn
-
Hello WebFlux 方法 hellowebflux-method
最小的 WebFlux 安全配置
以下列表显示了最低的 WebFlux Security 配置:
-
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 不会总是自动导入该方法,从而导致编译问题。 |
此配置显式设置了与我们的最小配置相同的所有内容。 从这里,您可以更轻松地更改默认值。
您可以在单元测试中找到更多显式配置示例,方法是搜索EnableWebFluxSecurity
在config/src/test/
目录.
多链支持
您可以配置多个SecurityWebFilterChain
实例,以分隔配置RequestMatcher
实例。
例如,您可以隔离以/api
:
-
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 | 配置一个SecurityWebFilterChain 使用@Order 指定哪个SecurityWebFilterChain Spring Security 应首先考虑 |
2 | 用PathPatternParserServerWebExchangeMatcher 声明这个SecurityWebFilterChain 仅适用于以/api/ |
3 | 指定将用于/api/** 端点 |
4 | 创建另一个SecurityWebFilterChain 优先级较低,以匹配所有其他网址 |
5 | 指定将用于应用程序其余部分的身份验证机制 |
Spring Security 选择一个SecurityWebFilterChain
@Bean
对于每个请求。
它按securityMatcher
定义。
在这种情况下,这意味着,如果 URL 路径以/api
,Spring Security 使用apiHttpSecurity
.
如果 URL 不是以/api
,Spring Security 默认为webHttpSecurity
,其中隐含的securityMatcher
与任何请求匹配。
模块化服务器Http安全配置
许多用户更喜欢他们的 Spring Security 配置位于集中位置,并选择在SecurityWebFilterChain
Bean 声明。
但是,有时用户可能希望将配置模块化。
这可以使用以下方法完成:
Customizer<ServerHttpSecurity> Bean
如果您想模块化安全配置,可以将逻辑放在Customizer<ServerHttpSecurity>
豆。
例如,以下配置将确保所有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 |
顶级服务器HttpSecurity 定制器 Bean
如果您希望进一步模块化安全配置,Spring Security 将自动应用任何顶级HttpSecurity
Customizer
豆。
顶级HttpSecurity
Customizer
类型可以概括为Customizer<T>
匹配public HttpSecurity.*(Customizer<T>)
.
这转化为任何Customizer<T>
这是公共方法的单个参数HttpSecurity
.
几个例子可以帮助澄清。
如果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'")
}
}
}
定制器豆子订购
首先,使用 ObjectProvider#orderedStream() 应用每个 Customizer<HttpSecurity> Bean。
这意味着,如果有多个Customizer<HttpSecurity>
Bean,可以将@Order注释添加到 Bean 定义中以控制排序。
接下来,查找每个顶级 HttpSecurity Customizer Bean 类型,并使用ObjectProvider#orderedStream()
.
如果有两个Customizer<HeadersConfigurer<HttpSecurity>>
豆子和两个Customizer<HttpsRedirectConfigurer<HttpSecurity>>
实例,每个Customizer
type 被调用为 undefined。
但是,每个实例的顺序Customizer<HttpsRedirectConfigurer<HttpSecurity>>
由ObjectProvider#orderedStream()
并且可以使用@Order
在 Bean 上定义。
最后,HttpSecurity
Bean 作为 Bean 注入。
都Customizer
实例在HttpSecurity
Bean 被创建。
这允许覆盖Customizer
豆。
您可以在下面找到一个示例来说明排序:
-
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 豆子的@Order 所以首先应用它。
如果没有@Order 注释Customizer<HttpSecurity> Beans 或@Order 注释具有相同的值,则Customizer<HttpSecurity> 实例被应用为未定义。 |
2 | 这userAuthorization 由于是Customizer<HttpSecurity> |
3 | 命令Customizer 类型未定义。
在此示例中,的顺序contentSecurityPolicy ,contentTypeOptions 和httpsRedirect 是未定义的。
如果@Order(Ordered.HIGHEST_PRECEDENCE) 被添加到contentTypeOptions ,那么我们就会知道contentTypeOptions 在contentSecurityPolicy (它们是同一类型),但我们不知道是否httpsRedirect 在Customizer<HeadersConfigurer<HttpSecurity>> 豆。 |
4 | 毕竟Customizer 应用 bean,则HttpSecurity 作为 Bean 传入。 |