webflux.adoc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. [[jc-webflux]]
  2. = WebFlux Security
  3. Spring Security's WebFlux support relies on a `WebFilter` and works the same for Spring WebFlux and Spring WebFlux.Fn.
  4. A few sample applications demonstrate the code:
  5. * Hello WebFlux {gh-samples-url}/reactive/webflux/java/hello-security[hellowebflux]
  6. * Hello WebFlux.Fn {gh-samples-url}/reactive/webflux-fn/hello-security[hellowebfluxfn]
  7. * Hello WebFlux Method {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method]
  8. == Minimal WebFlux Security Configuration
  9. The following listing shows a minimal WebFlux Security configuration:
  10. .Minimal WebFlux Security Configuration
  11. [tabs]
  12. ======
  13. Java::
  14. +
  15. [source,java,role="primary"]
  16. -----
  17. @Configuration
  18. @EnableWebFluxSecurity
  19. public class HelloWebfluxSecurityConfig {
  20. @Bean
  21. public MapReactiveUserDetailsService userDetailsService() {
  22. UserDetails user = User.withDefaultPasswordEncoder()
  23. .username("user")
  24. .password("user")
  25. .roles("USER")
  26. .build();
  27. return new MapReactiveUserDetailsService(user);
  28. }
  29. }
  30. -----
  31. Kotlin::
  32. +
  33. [source,kotlin,role="secondary"]
  34. -----
  35. @Configuration
  36. @EnableWebFluxSecurity
  37. class HelloWebfluxSecurityConfig {
  38. @Bean
  39. fun userDetailsService(): ReactiveUserDetailsService {
  40. val userDetails = User.withDefaultPasswordEncoder()
  41. .username("user")
  42. .password("user")
  43. .roles("USER")
  44. .build()
  45. return MapReactiveUserDetailsService(userDetails)
  46. }
  47. }
  48. -----
  49. ======
  50. 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.
  51. == Explicit WebFlux Security Configuration
  52. The following page shows an explicit version of the minimal WebFlux Security configuration:
  53. .Explicit WebFlux Security Configuration
  54. [tabs]
  55. ======
  56. Java::
  57. +
  58. [source,java,role="primary"]
  59. -----
  60. @Configuration
  61. @EnableWebFluxSecurity
  62. public class HelloWebfluxSecurityConfig {
  63. @Bean
  64. public MapReactiveUserDetailsService userDetailsService() {
  65. UserDetails user = User.withDefaultPasswordEncoder()
  66. .username("user")
  67. .password("user")
  68. .roles("USER")
  69. .build();
  70. return new MapReactiveUserDetailsService(user);
  71. }
  72. @Bean
  73. public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
  74. http
  75. .authorizeExchange((authorize) -> authorize
  76. .anyExchange().authenticated()
  77. )
  78. .httpBasic(withDefaults())
  79. .formLogin(withDefaults());
  80. return http.build();
  81. }
  82. }
  83. -----
  84. Kotlin::
  85. +
  86. [source,kotlin,role="secondary"]
  87. -----
  88. import org.springframework.security.config.web.server.invoke
  89. @Configuration
  90. @EnableWebFluxSecurity
  91. class HelloWebfluxSecurityConfig {
  92. @Bean
  93. fun userDetailsService(): ReactiveUserDetailsService {
  94. val userDetails = User.withDefaultPasswordEncoder()
  95. .username("user")
  96. .password("user")
  97. .roles("USER")
  98. .build()
  99. return MapReactiveUserDetailsService(userDetails)
  100. }
  101. @Bean
  102. fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  103. return http {
  104. authorizeExchange {
  105. authorize(anyExchange, authenticated)
  106. }
  107. formLogin { }
  108. httpBasic { }
  109. }
  110. }
  111. }
  112. -----
  113. ======
  114. [NOTE]
  115. 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.
  116. This configuration explicitly sets up all the same things as our minimal configuration.
  117. From here, you can more easily make changes to the defaults.
  118. 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].
  119. [[jc-webflux-multiple-filter-chains]]
  120. === Multiple Chains Support
  121. You can configure multiple `SecurityWebFilterChain` instances to separate configuration by `RequestMatcher` instances.
  122. For example, you can isolate configuration for URLs that start with `/api`:
  123. [tabs]
  124. ======
  125. Java::
  126. +
  127. [source,java,role="primary"]
  128. ----
  129. @Configuration
  130. @EnableWebFluxSecurity
  131. static class MultiSecurityHttpConfig {
  132. @Order(Ordered.HIGHEST_PRECEDENCE) <1>
  133. @Bean
  134. SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http) {
  135. http
  136. .securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/**")) <2>
  137. .authorizeExchange((authorize) -> authorize
  138. .anyExchange().authenticated()
  139. )
  140. .oauth2ResourceServer(OAuth2ResourceServerSpec::jwt); <3>
  141. return http.build();
  142. }
  143. @Bean
  144. SecurityWebFilterChain webHttpSecurity(ServerHttpSecurity http) { <4>
  145. http
  146. .authorizeExchange((authorize) -> authorize
  147. .anyExchange().authenticated()
  148. )
  149. .httpBasic(withDefaults()); <5>
  150. return http.build();
  151. }
  152. @Bean
  153. ReactiveUserDetailsService userDetailsService() {
  154. return new MapReactiveUserDetailsService(
  155. PasswordEncodedUser.user(), PasswordEncodedUser.admin());
  156. }
  157. }
  158. ----
  159. Kotlin::
  160. +
  161. [source,kotlin,role="secondary"]
  162. ----
  163. import org.springframework.security.config.web.server.invoke
  164. @Configuration
  165. @EnableWebFluxSecurity
  166. open class MultiSecurityHttpConfig {
  167. @Order(Ordered.HIGHEST_PRECEDENCE) <1>
  168. @Bean
  169. open fun apiHttpSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
  170. return http {
  171. securityMatcher(PathPatternParserServerWebExchangeMatcher("/api/**")) <2>
  172. authorizeExchange {
  173. authorize(anyExchange, authenticated)
  174. }
  175. oauth2ResourceServer {
  176. jwt { } <3>
  177. }
  178. }
  179. }
  180. @Bean
  181. open fun webHttpSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { <4>
  182. return http {
  183. authorizeExchange {
  184. authorize(anyExchange, authenticated)
  185. }
  186. httpBasic { } <5>
  187. }
  188. }
  189. @Bean
  190. open fun userDetailsService(): ReactiveUserDetailsService {
  191. return MapReactiveUserDetailsService(
  192. PasswordEncodedUser.user(), PasswordEncodedUser.admin()
  193. )
  194. }
  195. }
  196. ----
  197. ======
  198. <1> Configure a `SecurityWebFilterChain` with an `@Order` to specify which `SecurityWebFilterChain` Spring Security should consider first
  199. <2> Use `PathPatternParserServerWebExchangeMatcher` to state that this `SecurityWebFilterChain` will only apply to URL paths that start with `/api/`
  200. <3> Specify the authentication mechanisms that will be used for `/api/**` endpoints
  201. <4> Create another instance of `SecurityWebFilterChain` with lower precedence to match all other URLs
  202. <5> Specify the authentication mechanisms that will be used for the rest of the application
  203. Spring Security selects one `SecurityWebFilterChain` `@Bean` for each request.
  204. It matches the requests in order by the `securityMatcher` definition.
  205. In this case, that means that, if the URL path starts with `/api`, Spring Security uses `apiHttpSecurity`.
  206. If the URL does not start with `/api`, Spring Security defaults to `webHttpSecurity`, which has an implied `securityMatcher` that matches any request.
  207. [[modular-serverhttpsecurity-configuration]]
  208. == Modular ServerHttpSecurity Configuration
  209. Many users prefer that their Spring Security configuration lives in a centralized place and will choose to configure it within the `SecurityWebFilterChain` Bean declaration.
  210. However, there are times that users may want to modularize the configuration.
  211. This can be done using:
  212. * xref:#serverhttpsecurity-customizer-bean[Customizer<ServerHttpSecurity> Beans]
  213. * xref:#top-level-customizer-bean[Top Level ServerHttpSecurity Customizer Beans]
  214. // FIXME: this needs to link to appropriate spot
  215. // 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].
  216. [[serverhttpsecurity-customizer-bean]]
  217. === Customizer<ServerHttpSecurity> Beans
  218. If you would like to modularize your security configuration you can place logic in a `Customizer<ServerHttpSecurity>` Bean.
  219. For example, the following configuration will ensure all `ServerHttpSecurity` instances are configured to:
  220. include-code::./ServerHttpSecurityCustomizerBeanConfiguration[tag=httpSecurityCustomizer,indent=0]
  221. <1> Set the xref:servlet/exploits/headers.adoc#servlet-headers-csp[Content Security Policy] to `object-src 'none'`
  222. <2> xref:servlet/exploits/http.adoc#servlet-http-redirect[Redirect any request to https]
  223. [[top-level-customizer-bean]]
  224. === Top Level ServerHttpSecurity Customizer Beans
  225. If you prefer to have further modularization of your security configuration, Spring Security will automatically apply any top level `HttpSecurity` `Customizer` Beans.
  226. A top level `HttpSecurity` `Customizer` type can be summarized as any `Customizer<T>` that matches `public HttpSecurity.*(Customizer<T>)`.
  227. 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[].
  228. A few examples can help to clarify.
  229. 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`.
  230. 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)[].
  231. 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'`:
  232. include-code::./TopLevelCustomizerBeanConfiguration[tag=headersCustomizer,indent=0]
  233. [[customizer-bean-ordering]]
  234. === Customizer Bean Ordering
  235. 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()].
  236. 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.
  237. Next every xref:#top-level-customizer-bean[Top Level HttpSecurity Customizer Beans] type is looked up and each is is applied using `ObjectProvider#orderedStream()`.
  238. 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.
  239. 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.
  240. Finally, the `HttpSecurity` Bean is injected as a Bean.
  241. All `Customizer` instances are applied before the `HttpSecurity` Bean is created.
  242. This allows overriding the customizations provided by the `Customizer` Beans.
  243. You can find an example below that illustrates the ordering:
  244. include-code::./CustomizerBeanOrderingConfiguration[tag=sample,indent=0]
  245. <1> First all `Customizer<HttpSecurity>` instances are applied.
  246. The `adminAuthorization` Bean has the highest `@Order` so it is applied first.
  247. 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.
  248. <2> The `userAuthorization` is applied next due to being an instance of `Customizer<HttpSecurity>`
  249. <3> The order that the `Customizer` types are undefined.
  250. In this example, the order of `contentSecurityPolicy`, `contentTypeOptions`, and `httpsRedirect` are undefined.
  251. 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.
  252. <4> After all of the `Customizer` Beans are applied, the `HttpSecurity` is passed in as a Bean.