123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- [[jc-webflux]]
- = WebFlux Security
- Spring Security's WebFlux support relies on a `WebFilter` and works the same for Spring WebFlux and Spring WebFlux.Fn.
- A few sample applications demonstrate the code:
- * Hello WebFlux {gh-samples-url}/reactive/webflux/java/hello-security[hellowebflux]
- * Hello WebFlux.Fn {gh-samples-url}/reactive/webflux-fn/hello-security[hellowebfluxfn]
- * Hello WebFlux Method {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method]
- == Minimal WebFlux Security Configuration
- The following listing shows a minimal WebFlux Security configuration:
- .Minimal WebFlux Security Configuration
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- -----
- @Configuration
- @EnableWebFluxSecurity
- public class HelloWebfluxSecurityConfig {
- @Bean
- public MapReactiveUserDetailsService userDetailsService() {
- UserDetails user = User.withDefaultPasswordEncoder()
- .username("user")
- .password("user")
- .roles("USER")
- .build();
- return new MapReactiveUserDetailsService(user);
- }
- }
- -----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- -----
- @Configuration
- @EnableWebFluxSecurity
- class HelloWebfluxSecurityConfig {
- @Bean
- fun userDetailsService(): ReactiveUserDetailsService {
- val userDetails = User.withDefaultPasswordEncoder()
- .username("user")
- .password("user")
- .roles("USER")
- .build()
- return MapReactiveUserDetailsService(userDetails)
- }
- }
- -----
- ======
- This configuration provides form and HTTP basic authentication, sets up authorization to require an authenticated user for accessing any page, sets up a default login page and a default logout page, sets up security related HTTP headers, adds CSRF protection, and more.
- == Explicit WebFlux Security Configuration
- The following page shows an explicit version of the minimal WebFlux Security configuration:
- .Explicit WebFlux Security Configuration
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- -----
- @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();
- }
- }
- -----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- -----
- 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 { }
- }
- }
- }
- -----
- ======
- [NOTE]
- Make sure to import the `org.springframework.security.config.web.server.invoke` function to enable the Kotlin DSL in your class, as the IDE will not always auto-import the method, causing compilation issues.
- This configuration explicitly sets up all the same things as our minimal configuration.
- From here, you can more easily make changes to the defaults.
- You can find more examples of explicit configuration in unit tests, by searching for https://github.com/spring-projects/spring-security/search?q=path%3Aconfig%2Fsrc%2Ftest%2F+EnableWebFluxSecurity[`EnableWebFluxSecurity` in the `config/src/test/` directory].
- [[jc-webflux-multiple-filter-chains]]
- === Multiple Chains Support
- You can configure multiple `SecurityWebFilterChain` instances to separate configuration by `RequestMatcher` instances.
- For example, you can isolate configuration for URLs that start with `/api`:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @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());
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- 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> Configure a `SecurityWebFilterChain` with an `@Order` to specify which `SecurityWebFilterChain` Spring Security should consider first
- <2> Use `PathPatternParserServerWebExchangeMatcher` to state that this `SecurityWebFilterChain` will only apply to URL paths that start with `/api/`
- <3> Specify the authentication mechanisms that will be used for `/api/**` endpoints
- <4> Create another instance of `SecurityWebFilterChain` with lower precedence to match all other URLs
- <5> Specify the authentication mechanisms that will be used for the rest of the application
- Spring Security selects one `SecurityWebFilterChain` `@Bean` for each request.
- It matches the requests in order by the `securityMatcher` definition.
- In this case, that means that, if the URL path starts with `/api`, Spring Security uses `apiHttpSecurity`.
- If the URL does not start with `/api`, Spring Security defaults to `webHttpSecurity`, which has an implied `securityMatcher` that matches any request.
- [[modular-serverhttpsecurity-configuration]]
- == Modular ServerHttpSecurity Configuration
- Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it within the `SecurityWebFilterChain` Bean declaration.
- However, there are times that users may want to modularize the configuration.
- This can be done using:
- * xref:#serverhttpsecurity-customizer-bean[Customizer<ServerHttpSecurity> Beans]
- * xref:#top-level-customizer-bean[Top Level ServerHttpSecurity Customizer Beans]
- // FIXME: this needs to link to appropriate spot
- // NOTE: If you are using Spring Security's xref:servlet/configuration/kotlin.adoc[], then you can also expose `*Dsl -> Unit` Beans as outlined in xref:./kotlin.adoc#modular-httpsecuritydsl-configuration[Modular HttpSecurityDsl Configuration].
- [[serverhttpsecurity-customizer-bean]]
- === Customizer<ServerHttpSecurity> Beans
- If you would like to modularize your security configuration you can place logic in a `Customizer<ServerHttpSecurity>` Bean.
- For example, the following configuration will ensure all `ServerHttpSecurity` instances are configured to:
- include-code::./ServerHttpSecurityCustomizerBeanConfiguration[tag=httpSecurityCustomizer,indent=0]
- <1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
- <2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
- [[top-level-customizer-bean]]
- === Top Level ServerHttpSecurity Customizer Beans
- If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level `HttpSecurity` `Customizer` Beans.
- A top level `HttpSecurity` `Customizer` type can be summarized as any `Customizer<T>` that matches `public HttpSecurity.*(Customizer<T>)`.
- This translates to any `Customizer<T>` that is a single argument to a public method on javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity[].
- A few examples can help to clarify.
- If `Customizer<ContentTypeOptionsConfig>` is published as a Bean, it will not be be automatically applied because it is an argument to javadoc:org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#contentTypeOptions(org.springframework.security.config.Customizer)[] which is not a method defined on `HttpSecurity`.
- However, if `Customizer<HeadersConfigurer<HttpSecurity>>` is published as a Bean, it will be automatically applied because it is an argument to javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity#headers(org.springframework.security.config.Customizer)[].
- For example, the following configuration will ensure that the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] is set to `object-src 'none'`:
- include-code::./TopLevelCustomizerBeanConfiguration[tag=headersCustomizer,indent=0]
- [[customizer-bean-ordering]]
- === Customizer Bean Ordering
- First each xref:#httpsecurity-customizer-bean[Customizer<HttpSecurity> Bean] is applied using https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ObjectProvider.html#orderedStream()[ObjectProvider#orderedStream()].
- This means that if there are multiple `Customizer<HttpSecurity>` Beans, the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/Order.html[@Order] annotation can be added to the Bean definitions to control the ordering.
- Next every xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans] type is looked up and each is is applied using `ObjectProvider#orderedStream()`.
- If there is are two `Customizer<HeadersConfigurer<HttpSecurity>>` beans and two `Customizer<HttpsRedirectConfigurer<HttpSecurity>>` instances, the order that each `Customizer` type is invoked is undefined.
- However, the order that each instance of `Customizer<HttpsRedirectConfigurer<HttpSecurity>>` is defined by `ObjectProvider#orderedStream()` and can be controlled using `@Order` on the Bean the definitions.
- Finally, the `HttpSecurity` Bean is injected as a Bean.
- All `Customizer` instances are applied before the `HttpSecurity` Bean is created.
- This allows overriding the customizations provided by the `Customizer` Beans.
- You can find an example below that illustrates the ordering:
- include-code::./CustomizerBeanOrderingConfiguration[tag=sample,indent=0]
- <1> First all `Customizer<HttpSecurity>` instances are applied.
- The `adminAuthorization` Bean has the highest `@Order` so it is applied first.
- If there are no `@Order` annotations on the `Customizer<HttpSecurity>` Beans or the `@Order` annotations had the same value, then the order that the `Customizer<HttpSecurity>` instances are applied is undefined.
- <2> The `userAuthorization` is applied next due to being an instance of `Customizer<HttpSecurity>`
- <3> The order that the `Customizer` types are undefined.
- In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` are undefined.
- If `@Order(Ordered.HIGHEST_PRECEDENCE)` was added to `contentTypeOptions`, then we would know that `contentTypeOptions` is before `contentSecurityPolicy` (they are the same type), but we do not know if `httpsRedirect` is before or after the `Customizer<HeadersConfigurer<HttpSecurity>>` Beans.
- <4> After all of the `Customizer` Beans are applied, the `HttpSecurity` is passed in as a Bean.
|