此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4spring-doc.cadn.net.cn

WebFlux 安全

Spring Security 的 WebFlux 支持依赖于一个 WebFilter,并且在 Spring WebFlux 和 Spring WebFlux.Fn 中的工作方式相同。 一些示例应用展示了相关的代码:spring-doc.cadn.net.cn

最小的WebFlux安全配置

以下示例显示了最小化的WebFlux安全配置:spring-doc.cadn.net.cn

最小的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);
	}
}
@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保护等。spring-doc.cadn.net.cn

显式的WebFlux 安全配置

以下页面展示了一个显式的最小WebFlux 安全配置版本:spring-doc.cadn.net.cn

显式的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();
	}
}
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不会总是自动导入该方法,可能会导致编译问题。

此配置显式设置了所有与我们最小化配置相同的内容。 从这里,您可以更轻松地更改默认设置。spring-doc.cadn.net.cn

您可以在单元测试中找到更多显式配置的示例,只需在 config/src/test/ 目录中搜索 EnableWebFluxSecurity 即可。spring-doc.cadn.net.cn

支持多链

你可以通过SecurityWebFilterChain实例来配置多个RequestMatcher实例,以实现不同的安全配置。spring-doc.cadn.net.cn

例如,您可以隔离以/api开头的URL的配置:spring-doc.cadn.net.cn

@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,以指定Spring Security应该首先考虑哪个SecurityWebFilterChain
2 使用PathPatternParserServerWebExchangeMatcher来声明这个SecurityWebFilterChain仅应用于以/api/开头的URL路径。
3 指定将用于 /api/** 端点的身份验证机制
4 创建另一个具有较低优先级的SecurityWebFilterChain实例,以匹配所有其他URL
5 指定将用于整个应用程序的身份验证机制

Spring Security 为每个请求选择一个 SecurityWebFilterChain @Bean。 它按照 securityMatcher 定义的顺序匹配请求。spring-doc.cadn.net.cn

在这种情况中,这意味着如果 URL 路径以 /api 开始,Spring Security 使用 apiHttpSecurity。 否则,如果 URL 不以 /api 开始,Spring Security 默认使用 webHttpSecurity,后者具有隐含的 securityMatcher,匹配任何请求。spring-doc.cadn.net.cn

模块化 ServerHttpSecurity 配置

许多用户更喜欢将他们的 Spring Security 配置集中在一个地方,并选择在 SecurityWebFilterChain Bean 声明中进行配置。 然而,有时用户可能会希望模块化配置。这可以通过以下方式实现:spring-doc.cadn.net.cn

自定义化器<ServerHttpSecurity> Bean

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

@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 设置xref page../../servlet/exploits/headers.html#servlet-headers-csp
2 将所有请求重定向到 https

顶级ServerHttpSecurity自定义Bean

如果您希望进一步模块化您的安全配置,Spring Security 将会自动应用任何顶级的 HttpSecurity Customizer Bean。spring-doc.cadn.net.cn

顶级 HttpSecurity Customizer 类型可概括为任何匹配 public HttpSecurity.*(Customizer<T>)Customizer<T>。 这等同于在 HttpSecurity 上作为公共方法单个参数的任何 Customizer<T>spring-doc.cadn.net.cn

一些示例有助于澄清。 如果 Customizer<ContentTypeOptionsConfig> 作为 Bean 发布,它将不会被自动应用,因为它是 HeadersConfigurer.contentTypeOptions(Customizer) 的参数,而该参数并非在 HttpSecurity 上定义的方法。 然而,如果 Customizer<HeadersConfigurer<HttpSecurity>> 作为 Bean 发布,它将被自动应用,因为它是 HttpSecurity.headers(Customizer) 的参数。spring-doc.cadn.net.cn

例如,以下配置将确保xref page设置为../../servlet/exploits/headers.html#servlet-headers-csp: spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

@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'")
        }
    }
}

自定义 Bean 排序

首先,每个Customizer<HttpSecurity> Bean将使用ObjectProvider#orderedStream()应用。 这意味着如果存在多个#httpsecurity-customizer-bean Bean,可以通过为Bean定义添加@Order注解来控制排序顺序。spring-doc.cadn.net.cn

接下来,会查找所有#top-level-customizer-bean类型,并依次应用这些Bean,使用ObjectProvider#orderedStream()。 如果有两个Customizer<HeadersConfigurer<HttpSecurity>> Bean 和两个Customizer<HttpsRedirectConfigurer<HttpSecurity>>实例,则每个Customizer类型的调用顺序是未定义的。 但是,可以通过在Bean定义上使用Customizer<HttpsRedirectConfigurer<HttpSecurity>>来控制ObjectProvider#orderedStream()中定义的每个@Order实例的顺序。spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

spring-doc.cadn.net.cn

最终,HttpSecurity Bean 作为 Bean 注入。 所有 Customizer 实例在创建 HttpSecurity Bean 之前被应用。 这允许覆盖由 Customizer Bean 提供的自定义设置。spring-doc.cadn.net.cn

您可以在下面找到一个示例,以说明排序:spring-doc.cadn.net.cn

@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 注解,因此它会被优先应用。 如果没有在 @Order Beans 上使用 Customizer<HttpSecurity> 注解,或者这些 @Order 注解的值相同,则 Customizer<HttpSecurity> 实例的应用顺序是未定义的。
2 userAuthorization 应用于此,因为它是 Customizer<HttpSecurity> 的一个实例。
3 Customizer 类型的顺序是未定义的。 在此示例中,contentSecurityPolicycontentTypeOptionshttpsRedirect 的顺序是未定义的。 如果将 @Order(Ordered.HIGHEST_PRECEDENCE) 添加到 contentTypeOptions 中,那么我们将知道 contentTypeOptionscontentSecurityPolicy 之前(它们是同一类型),但我们不知道 httpsRedirect 是在 Customizer<HeadersConfigurer<HttpSecurity>> Bean 之前还是之后。
4 在应用了所有 Customizer 颗粒之后,HttpSecurity 会被作为颗粒传递进去。