reactive.adoc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. = Reactive Migrations
  2. If you have already performed the xref:migration/index.adoc[initial migration steps] for your Reactive application, you're now ready to perform steps specific to Reactive applications.
  3. == Exploit Protection Migrations
  4. The following steps relate to changes around how to configure CSRF.
  5. === Configure `tokenFromMultipartDataEnabled`
  6. In Spring Security 5.8, the method `tokenFromMultipartDataEnabled` was deprecated in favor of `ServerCsrfTokenRequestAttributeHandler#setTokenFromMultipartDataEnabled`.
  7. To address the deprecation, the following code:
  8. .Configure `tokenFromMultipartDataEnabled` with DSL
  9. [tabs]
  10. ======
  11. Java::
  12. +
  13. [source,java,role="primary"]
  14. ----
  15. @Bean
  16. SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  17. http
  18. // ...
  19. .csrf((csrf) -> csrf
  20. .tokenFromMultipartDataEnabled(true)
  21. );
  22. return http.build();
  23. }
  24. ----
  25. Kotlin::
  26. +
  27. [source,kotlin,role="secondary"]
  28. ----
  29. @Bean
  30. open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  31. return http {
  32. // ...
  33. csrf {
  34. tokenFromMultipartDataEnabled = true
  35. }
  36. }
  37. }
  38. ----
  39. ======
  40. can be replaced with:
  41. .Configure `tokenFromMultipartDataEnabled` with `ServerCsrfTokenRequestAttributeHandler`
  42. [tabs]
  43. ======
  44. Java::
  45. +
  46. [source,java,role="primary"]
  47. ----
  48. @Bean
  49. SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  50. ServerCsrfTokenRequestAttributeHandler requestHandler = new ServerCsrfTokenRequestAttributeHandler();
  51. requestHandler.setTokenFromMultipartDataEnabled(true);
  52. http
  53. // ...
  54. .csrf((csrf) -> csrf
  55. .csrfTokenRequestHandler(requestHandler)
  56. );
  57. return http.build();
  58. }
  59. ----
  60. Kotlin::
  61. +
  62. [source,kotlin,role="secondary"]
  63. ----
  64. @Bean
  65. open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  66. val requestHandler = ServerCsrfTokenRequestAttributeHandler()
  67. requestHandler.tokenFromMultipartDataEnabled = true
  68. return http {
  69. // ...
  70. csrf {
  71. csrfTokenRequestHandler = requestHandler
  72. }
  73. }
  74. }
  75. ----
  76. ======
  77. === Protect against CSRF BREACH
  78. You can opt into Spring Security 6's default support for BREACH protection of the `CsrfToken` using the following configuration:
  79. .`CsrfToken` BREACH Protection
  80. [tabs]
  81. ======
  82. Java::
  83. +
  84. [source,java,role="primary"]
  85. ----
  86. @Bean
  87. SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  88. XorServerCsrfTokenRequestAttributeHandler requestHandler = new XorServerCsrfTokenRequestAttributeHandler();
  89. // ...
  90. http
  91. // ...
  92. .csrf((csrf) -> csrf
  93. .csrfTokenRequestHandler(requestHandler)
  94. );
  95. return http.build();
  96. }
  97. ----
  98. Kotlin::
  99. +
  100. [source,kotlin,role="secondary"]
  101. ----
  102. @Bean
  103. open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  104. val requestHandler = XorServerCsrfTokenRequestAttributeHandler()
  105. // ...
  106. return http {
  107. // ...
  108. csrf {
  109. csrfTokenRequestHandler = requestHandler
  110. }
  111. }
  112. }
  113. ----
  114. ======
  115. [[reactive-csrf-breach-opt-out]]
  116. === Opt-out Steps
  117. If configuring CSRF BREACH protection gives you trouble, take a look at these scenarios for optimal opt out behavior:
  118. ==== I am using AngularJS or another Javascript framework
  119. If you are using AngularJS and the https://angular.io/api/common/http/HttpClientXsrfModule[HttpClientXsrfModule] (or a similar module in another framework) along with `CookieServerCsrfTokenRepository.withHttpOnlyFalse()`, you may find that automatic support no longer works.
  120. In this case, you can configure Spring Security to validate the raw `CsrfToken` from the cookie while keeping CSRF BREACH protection of the response using a custom `ServerCsrfTokenRequestHandler` with delegation, like so:
  121. .Configure `CsrfToken` BREACH Protection to validate raw tokens
  122. [tabs]
  123. ======
  124. Java::
  125. +
  126. [source,java,role="primary"]
  127. ----
  128. @Bean
  129. SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  130. CookieServerCsrfTokenRepository tokenRepository = CookieServerCsrfTokenRepository.withHttpOnlyFalse();
  131. XorServerCsrfTokenRequestAttributeHandler delegate = new XorServerCsrfTokenRequestAttributeHandler();
  132. // Use only the handle() method of XorServerCsrfTokenRequestAttributeHandler and the
  133. // default implementation of resolveCsrfTokenValue() from ServerCsrfTokenRequestHandler
  134. ServerCsrfTokenRequestHandler requestHandler = delegate::handle;
  135. http
  136. // ...
  137. .csrf((csrf) -> csrf
  138. .csrfTokenRepository(tokenRepository)
  139. .csrfTokenRequestHandler(requestHandler)
  140. );
  141. return http.build();
  142. }
  143. @Bean
  144. WebFilter csrfCookieWebFilter() {
  145. return (exchange, chain) -> {
  146. Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
  147. return csrfToken.doOnSuccess(token -> {
  148. /* Ensures the token is subscribed to. */
  149. }).then(chain.filter(exchange));
  150. };
  151. }
  152. ----
  153. Kotlin::
  154. +
  155. [source,kotlin,role="secondary"]
  156. ----
  157. @Bean
  158. open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  159. val tokenRepository = CookieServerCsrfTokenRepository.withHttpOnlyFalse()
  160. val delegate = XorServerCsrfTokenRequestAttributeHandler()
  161. // Use only the handle() method of XorServerCsrfTokenRequestAttributeHandler and the
  162. // default implementation of resolveCsrfTokenValue() from ServerCsrfTokenRequestHandler
  163. val requestHandler = ServerCsrfTokenRequestHandler(delegate::handle)
  164. return http.invoke {
  165. // ...
  166. csrf {
  167. csrfTokenRepository = tokenRepository
  168. csrfTokenRequestHandler = requestHandler
  169. }
  170. }
  171. }
  172. @Bean
  173. fun csrfCookieWebFilter(): WebFilter {
  174. return WebFilter { exchange, chain ->
  175. val csrfToken = exchange.getAttribute<Mono<CsrfToken>>(CsrfToken::class.java.name) ?: Mono.empty()
  176. csrfToken.doOnSuccess {
  177. /* Ensures the token is subscribed to. */
  178. }.then(chain.filter(exchange))
  179. }
  180. }
  181. ----
  182. ======
  183. ==== I need to opt out of CSRF BREACH protection for another reason
  184. If CSRF BREACH protection does not work for you for another reason, you can opt out using the following configuration:
  185. .Opt out of `CsrfToken` BREACH protection
  186. [tabs]
  187. ======
  188. Java::
  189. +
  190. [source,java,role="primary"]
  191. ----
  192. @Bean
  193. SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  194. ServerCsrfTokenRequestAttributeHandler requestHandler = new ServerCsrfTokenRequestAttributeHandler();
  195. http
  196. // ...
  197. .csrf((csrf) -> csrf
  198. .csrfTokenRequestHandler(requestHandler)
  199. );
  200. return http.build();
  201. }
  202. ----
  203. Kotlin::
  204. +
  205. [source,kotlin,role="secondary"]
  206. ----
  207. @Bean
  208. open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  209. val requestHandler = ServerCsrfTokenRequestAttributeHandler()
  210. return http {
  211. // ...
  212. csrf {
  213. csrfTokenRequestHandler = requestHandler
  214. }
  215. }
  216. }
  217. ----
  218. ======
  219. == Use `AuthorizationManager` for Method Security
  220. xref:reactive/authorization/method.adoc[Method Security] has been xref:reactive/authorization/method.adoc#jc-enable-reactive-method-security-authorization-manager[improved] through {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[the `AuthorizationManager` API] and direct use of Spring AOP.
  221. Should you run into trouble with making these changes, you can follow the
  222. <<reactive-authorizationmanager-methods-opt-out,opt out steps>> at the end of this section.
  223. In Spring Security 5.8, `useAuthorizationManager` was added to {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurity.html[`@EnableReactiveMethodSecurity`] to allow applications to opt in to ``AuthorizationManager``'s features.
  224. [[reactive-change-to-useauthorizationmanager]]
  225. === Change `useAuthorizationManager` to `true`
  226. To opt in, change `useAuthorizationManager` to `true` like so:
  227. [tabs]
  228. ======
  229. Java::
  230. +
  231. [source,java,role="primary"]
  232. ----
  233. @EnableReactiveMethodSecurity
  234. ----
  235. Kotlin::
  236. +
  237. [source,kotlin,role="secondary"]
  238. ----
  239. @EnableReactiveMethodSecurity
  240. ----
  241. ======
  242. changes to:
  243. [tabs]
  244. ======
  245. Java::
  246. +
  247. [source,java,role="primary"]
  248. ----
  249. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  250. ----
  251. Kotlin::
  252. +
  253. [source,kotlin,role="secondary"]
  254. ----
  255. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  256. ----
  257. ======
  258. [[reactive-check-for-annotationconfigurationexceptions]]
  259. === Check for ``AnnotationConfigurationException``s
  260. `useAuthorizationManager` activates stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
  261. If after turning on `useAuthorizationManager` you see ``AnnotationConfigurationException``s in your logs, follow the instructions in the exception message to clean up your application's method security annotation usage.
  262. [[reactive-authorizationmanager-methods-opt-out]]
  263. === Opt-out Steps
  264. If you ran into trouble with `AuthorizationManager` for reactive method security, you can opt out by changing:
  265. [tabs]
  266. ======
  267. Java::
  268. +
  269. [source,java,role="primary"]
  270. ----
  271. @EnableReactiveMethodSecurity
  272. ----
  273. Kotlin::
  274. +
  275. [source,kotlin,role="secondary"]
  276. ----
  277. @EnableReactiveMethodSecurity
  278. ----
  279. ======
  280. to:
  281. [tabs]
  282. ======
  283. Java::
  284. +
  285. [source,java,role="primary"]
  286. ----
  287. @EnableReactiveMethodSecurity(useAuthorizationManager = false)
  288. ----
  289. Kotlin::
  290. +
  291. [source,kotlin,role="secondary"]
  292. ----
  293. @EnableReactiveMethodSecurity(useAuthorizationManager = false)
  294. ----
  295. ======
  296. == Propagate ``AuthenticationServiceException``s
  297. {security-api-url}org/springframework/security/web/server/Webauthentication/AuthenticationWebFilter.html[`AuthenticationFilter`] propagates {security-api-url}org/springframework/security/authentication/AuthenticationServiceException.html[``AuthenticationServiceException``]s to the {security-api-url}org/springframework/security/web/server/ServerAuthenticationEntryPoint.html[`ServerAuthenticationEntryPoint`].
  298. Because ``AuthenticationServiceException``s represent a server-side error instead of a client-side error, in 6.0, this changes to propagate them to the container.
  299. === Configure `ServerAuthenticationFailureHandler` to rethrow ``AuthenticationServiceException``s
  300. To prepare for the 6.0 default, `httpBasic` and `oauth2ResourceServer` should be configured to rethrow ``AuthenticationServiceException``s.
  301. For each, construct the appropriate authentication entry point for `httpBasic` and for `oauth2ResourceServer`:
  302. [tabs]
  303. ======
  304. Java::
  305. +
  306. [source,java,role="primary"]
  307. ----
  308. ServerAuthenticationEntryPoint bearerEntryPoint = new BearerTokenServerAuthenticationEntryPoint();
  309. ServerAuthenticationEntryPoint basicEntryPoint = new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED);
  310. ----
  311. Kotlin::
  312. +
  313. [source,kotlin,role="secondary"]
  314. ----
  315. val bearerEntryPoint: ServerAuthenticationEntryPoint = BearerTokenServerAuthenticationEntryPoint()
  316. val basicEntryPoint: ServerAuthenticationEntryPoint = HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)
  317. ----
  318. ======
  319. [NOTE]
  320. ====
  321. If you use a custom `AuthenticationEntryPoint` for either or both mechanisms, use that one instead for the remaining steps.
  322. ====
  323. Then, construct and configure a `ServerAuthenticationEntryPointFailureHandler` for each one:
  324. [tabs]
  325. ======
  326. Java::
  327. +
  328. [source,java,role="primary"]
  329. ----
  330. AuthenticationFailureHandler bearerFailureHandler = new ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint);
  331. bearerFailureHandler.setRethrowAuthenticationServiceException(true);
  332. AuthenticationFailureHandler basicFailureHandler = new ServerAuthenticationEntryPointFailureHandler(basicEntryPoint);
  333. basicFailureHandler.setRethrowAuthenticationServiceException(true)
  334. ----
  335. Kotlin::
  336. +
  337. [source,kotlin,role="secondary"]
  338. ----
  339. val bearerFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint)
  340. bearerFailureHandler.setRethrowAuthenticationServiceException(true)
  341. val basicFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(basicEntryPoint)
  342. basicFailureHandler.setRethrowAuthenticationServiceException(true)
  343. ----
  344. ======
  345. Finally, wire each authentication failure handler into the DSL, like so:
  346. [tabs]
  347. ======
  348. Java::
  349. +
  350. [source,java,role="primary"]
  351. ----
  352. http
  353. .httpBasic((basic) -> basic.authenticationFailureHandler(basicFailureHandler))
  354. .oauth2ResourceServer((oauth2) -> oauth2.authenticationFailureHandler(bearerFailureHandler))
  355. ----
  356. Kotlin::
  357. +
  358. [source,kotlin,role="secondary"]
  359. ----
  360. http {
  361. httpBasic {
  362. authenticationFailureHandler = basicFailureHandler
  363. }
  364. oauth2ResourceServer {
  365. authenticationFailureHandler = bearerFailureHandler
  366. }
  367. }
  368. ----
  369. ======
  370. [[reactive-authenticationfailurehandler-opt-out]]
  371. === Opt-out Steps
  372. To opt-out of the 6.0 defaults and instead continue to pass `AuthenticationServiceException` on to ``ServerAuthenticationEntryPoint``s, you can follow the same steps as above, except set `rethrowAuthenticationServiceException` to false.
  373. [[add-configuration-annotation]]
  374. == Add `@Configuration` annotation
  375. In 6.0, `@Configuration` is removed from `@EnableWebFluxSecurity` and `@EnableReactiveMethodSecurity`.
  376. To prepare for this, wherever you are using one of these annotations, you may need to add `@Configuration`.
  377. For example, `@EnableReactiveMethodSecurity` changes from:
  378. [tabs]
  379. ======
  380. Java::
  381. +
  382. [source,java,role="primary"]
  383. ----
  384. @EnableReactiveMethodSecurity
  385. public class MyConfiguration {
  386. // ...
  387. }
  388. ----
  389. ======
  390. [tabs]
  391. ======
  392. Kotlin::
  393. +
  394. [source,java,role="primary"]
  395. ----
  396. @EnableReactiveMethodSecurity
  397. open class MyConfiguration {
  398. // ...
  399. }
  400. ----
  401. ======
  402. to:
  403. [tabs]
  404. ======
  405. Java::
  406. +
  407. [source,java,role="primary"]
  408. ----
  409. @Configuration
  410. @EnableReactiveMethodSecurity
  411. public class MyConfiguration {
  412. // ...
  413. }
  414. ----
  415. ======
  416. [tabs]
  417. ======
  418. Kotlin::
  419. +
  420. [source,java,role="primary"]
  421. ----
  422. @Configuration
  423. @EnableReactiveMethodSecurity
  424. open class MyConfiguration {
  425. // ...
  426. }
  427. ----
  428. ======
  429. == Address OAuth2 Client Deprecations
  430. === `ServerOAuth2AuthorizedClientExchangeFilterFunction`
  431. The method `setAccessTokenExpiresSkew(...)` can be replaced with one of:
  432. * `ClientCredentialsReactiveOAuth2AuthorizedClientProvider#setClockSkew(...)`
  433. * `RefreshTokenReactiveOAuth2AuthorizedClientProvider#setClockSkew(...)`
  434. * `JwtBearerReactiveOAuth2AuthorizedClientProvider#setClockSkew(...)`
  435. The method `setClientCredentialsTokenResponseClient(...)` can be replaced with the constructor `ServerOAuth2AuthorizedClientExchangeFilterFunction(ReactiveOAuth2AuthorizedClientManager)`.
  436. [NOTE]
  437. ====
  438. See xref:reactive/oauth2/client/authorization-grants.adoc#oauth2Client-client-creds-grant[Client Credentials] for more information.
  439. ====
  440. === `WebSessionOAuth2ServerAuthorizationRequestRepository`
  441. The method `setAllowMultipleAuthorizationRequests(...)` has no direct replacement.
  442. === `UnAuthenticatedServerOAuth2AuthorizedClientRepository`
  443. The class `UnAuthenticatedServerOAuth2AuthorizedClientRepository` has no direct replacement. Usage of the class can be replaced with `AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager`.
  444. == Add `@Configuration` to `@Enable*` annotations
  445. In 6.0, all Spring Security's `@Enable*` annotations had their `@Configuration` removed.
  446. While convenient, it was not consistent with the rest of the Spring projects and most notably Spring Framework's `@Enable*` annotations.
  447. Additionally, the introduction of support for `@Configuration(proxyBeanMethods=false)` in Spring Framework provides another reason to remove `@Configuration` meta-annotation from Spring Security's `@Enable*` annotations and allow users to opt into their preferred configuration mode.
  448. The following annotations had their `@Configuration` removed:
  449. - `@EnableGlobalAuthentication`
  450. - `@EnableGlobalMethodSecurity`
  451. - `@EnableMethodSecurity`
  452. - `@EnableReactiveMethodSecurity`
  453. - `@EnableWebSecurity`
  454. - `@EnableWebFluxSecurity`
  455. For example, if you are using `@EnableWebFluxSecurity`, you will need to change:
  456. [tabs]
  457. ======
  458. Java::
  459. +
  460. [source,java,role="primary"]
  461. ----
  462. @EnableWebFluxSecurity
  463. public class SecurityConfig {
  464. // ...
  465. }
  466. ----
  467. ======
  468. to:
  469. [tabs]
  470. ======
  471. Java::
  472. +
  473. [source,java,role="primary"]
  474. ----
  475. @Configuration
  476. @EnableWebFluxSecurity
  477. public class SecurityConfig {
  478. // ...
  479. }
  480. ----
  481. ======
  482. And the same applies to every other annotation listed above.