migration.adoc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. [[migration]]
  2. = Migrating to 6.0
  3. The Spring Security team has prepared the 5.8 release to simplify upgrading to Spring Security 6.0.
  4. Use 5.8 and the steps below to minimize changes when
  5. ifdef::spring-security-version[]
  6. xref:6.0.0@migration.adoc[updating to 6.0]
  7. endif::[]
  8. ifndef::spring-security-version[]
  9. updating to 6.0
  10. endif::[]
  11. .
  12. == Servlet
  13. === Explicit Save SecurityContextRepository
  14. In Spring Security 5, the default behavior is for the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[`SecurityContext`] to automatically be saved to the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] using the xref:servlet/authentication/persistence.adoc#securitycontextpersistencefilter[`SecurityContextPersistenceFilter`].
  15. Saving must be done just prior to the `HttpServletResponse` being committed and just before `SecurityContextPersistenceFilter`.
  16. Unfortunately, automatic persistence of the `SecurityContext` can surprise users when it is done prior to the request completing (i.e. just prior to committing the `HttpServletResponse`).
  17. It also is complex to keep track of the state to determine if a save is necessary causing unnecessary writes to the `SecurityContextRepository` (i.e. `HttpSession`) at times.
  18. In Spring Security 6, the default behavior is that the xref:servlet/authentication/persistence.adoc#securitycontextholderfilter[`SecurityContextHolderFilter`] will only read the `SecurityContext` from `SecurityContextRepository` and populate it in the `SecurityContextHolder`.
  19. Users now must explicitly save the `SecurityContext` with the `SecurityContextRepository` if they want the `SecurityContext` to persist between requests.
  20. This removes ambiguity and improves performance by only requiring writing to the `SecurityContextRepository` (i.e. `HttpSession`) when it is necessary.
  21. To opt into the new Spring Security 6 default, the following configuration can be used.
  22. include::partial$servlet/architecture/security-context-explicit.adoc[]
  23. [[requestcache-query-optimization]]
  24. === Optimize Querying of `RequestCache`
  25. In Spring Security 5, the default behavior is to query the xref:servlet/architecture.adoc#savedrequests[saved request] on every request.
  26. This means that in a typical setup, that in order to use the xref:servlet/architecture.adoc#requestcache[`RequestCache`] the `HttpSession` is queried on every request.
  27. In Spring Security 6, the default is that `RequestCache` will only be queried for a cached request if the HTTP parameter `continue` is defined.
  28. This allows Spring Security to avoid unnecessarily reading the `HttpSession` with the `RequestCache`.
  29. In Spring Security 5 the default is to use `HttpSessionRequestCache` which will be queried for a cached request on every request.
  30. If you are not overriding the defaults (i.e. using `NullRequestCache`), then the following configuration can be used to explicitly opt into the Spring Security 6 behavior in Spring Security 5.8:
  31. include::partial$servlet/architecture/request-cache-continue.adoc[]
  32. === Use `AuthorizationManager` for Method Security
  33. xref:servlet/authorization/method-security.adoc[Method Security] has been xref:servlet/authorization/method-security.adoc#jc-enable-method-security[simplified] through {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[the `AuthorizationManager` API] and direct use of Spring AOP.
  34. '''
  35. [[servlet-replace-globalmethodsecurity-with-methodsecurity]]
  36. ==== Replace xref:servlet/authorization/method-security.adoc#jc-enable-global-method-security[global method security] with xref:servlet/authorization/method-security.adoc#jc-enable-method-security[method security]
  37. {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.html[`@EnableGlobalMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-global-method-security[`<global-method-security>`] are deprecated in favor of {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.html[`@EnableMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-method-security[`<method-security>`], respectively.
  38. The new annotation and XML element activate Spring's xref:servlet/authorization/method-security.adoc#jc-enable-method-security[pre-post annotations] by default and use `AuthorizationManager` internally.
  39. This means that the following two listings are functionally equivalent:
  40. ====
  41. .Java
  42. [source,java,role="primary"]
  43. ----
  44. @EnableGlobalMethodSecurity(prePostEnabled = true)
  45. ----
  46. .Kotlin
  47. [source,kotlin,role="secondary"]
  48. ----
  49. @EnableGlobalMethodSecurity(prePostEnabled = true)
  50. ----
  51. .Xml
  52. [source,xml,role="secondary"]
  53. ----
  54. <global-method-security pre-post-enabled="true"/>
  55. ----
  56. ====
  57. and:
  58. ====
  59. .Java
  60. [source,java,role="primary"]
  61. ----
  62. @EnableMethodSecurity
  63. ----
  64. .Kotlin
  65. [source,kotlin,role="secondary"]
  66. ----
  67. @EnableMethodSecurity
  68. ----
  69. .Xml
  70. [source,xml,role="secondary"]
  71. ----
  72. <method-security/>
  73. ----
  74. ====
  75. For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior.
  76. For example, a listing like:
  77. ====
  78. .Java
  79. [source,java,role="primary"]
  80. ----
  81. @EnableGlobalMethodSecurity(securedEnabled = true)
  82. ----
  83. .Kotlin
  84. [source,kotlin,role="secondary"]
  85. ----
  86. @EnableGlobalMethodSecurity(securedEnabled = true)
  87. ----
  88. .Xml
  89. [source,xml,role="secondary"]
  90. ----
  91. <global-method-security secured-enabled="true"/>
  92. ----
  93. ====
  94. should change to:
  95. ====
  96. .Java
  97. [source,java,role="primary"]
  98. ----
  99. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  100. ----
  101. .Kotlin
  102. [source,kotlin,role="secondary"]
  103. ----
  104. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  105. ----
  106. .Xml
  107. [source,xml,role="secondary"]
  108. ----
  109. <method-security secured-enabled="true" pre-post-enabled="false"/>
  110. ----
  111. ====
  112. '''
  113. [[servlet-replace-permissionevaluator-bean-with-methodsecurityexpression-handler]]
  114. ==== Publish a `MethodSecurityExpressionHandler` instead of a `PermissionEvaluator`
  115. `@EnableMethodSecurity` does not pick up a `PermissionEvaluator`.
  116. This helps keep its API simple.
  117. If you have a custom {security-api-url}org/springframework/security/access/PermissionEvaluator.html[`PermissionEvaluator`] `@Bean`, please change it from:
  118. ====
  119. .Java
  120. [source,java,role="primary"]
  121. ----
  122. @Bean
  123. static PermissionEvaluator permissionEvaluator() {
  124. // ... your evaluator
  125. }
  126. ----
  127. .Kotlin
  128. [source,kotlin,role="secondary"]
  129. ----
  130. companion object {
  131. @Bean
  132. fun permissionEvaluator(): PermissionEvaluator {
  133. // ... your evaluator
  134. }
  135. }
  136. ----
  137. ====
  138. to:
  139. ====
  140. .Java
  141. [source,java,role="primary"]
  142. ----
  143. @Bean
  144. static MethodSecurityExpressionHandler expressionHandler() {
  145. var expressionHandler = new DefaultMethodSecurityExpressionHandler();
  146. expressionHandler.setPermissionEvaluator(myPermissionEvaluator);
  147. return expressionHandler;
  148. }
  149. ----
  150. .Kotlin
  151. [source,kotlin,role="secondary"]
  152. ----
  153. companion object {
  154. @Bean
  155. fun expressionHandler(): MethodSecurityExpressionHandler {
  156. val expressionHandler = DefaultMethodSecurityExpressionHandler
  157. expressionHandler.setPermissionEvaluator(myPermissionEvaluator)
  158. return expressionHandler
  159. }
  160. }
  161. ----
  162. ====
  163. '''
  164. [[servlet-check-for-annotationconfigurationexceptions]]
  165. ==== Check for ``AnnotationConfigurationException``s
  166. `@EnableMethodSecurity` and `<method-security>` activate stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
  167. If after moving to either you see ``AnnotationConfigurationException``s in your logs, follow the instructions in the exception message to clean up your application's method security annotation usage.
  168. === Use `AuthorizationManager` for Message Security
  169. xref:servlet/integrations/websocket.adoc[Message Security] has been xref:servlet/integrations/websocket.adoc#websocket-configuration[improved] through {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[the `AuthorizationManager` API] and direct use of Spring AOP.
  170. ==== Declare the 5.8 default
  171. In case you run into trouble with the ensuing steps and cannot use `AuthorizationManager` at this time, it's recommended as a first step to declare you are using the 5.8 default so that 5.8 behavior is preserved when you update.
  172. The only default to change for Method Security is if you are using `<websocket-message-broker>` in which case you will change:
  173. ====
  174. .Xml
  175. [source,xml,role="secondary"]
  176. ----
  177. <websocket-message-broker>
  178. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  179. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  180. </websocket-message-broker>
  181. ----
  182. ====
  183. to:
  184. ====
  185. .Xml
  186. [source,xml,role="secondary"]
  187. ----
  188. <websocket-message-broker use-authorization-manager="false">
  189. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  190. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  191. </websocket-message-broker>
  192. ----
  193. ====
  194. Later steps will turn this value back on, but now your code is minimally ready for upgrading in case you run into trouble with the remaining steps.
  195. ==== Ensure all messages have defined authorization rules
  196. The now-deprecated {security-api-url}org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.html[message security support] permits all messages by default.
  197. xref:servlet/integrations/websocket.adoc[The new support] has the stronger default of denying all messages.
  198. To prepare for this, ensure that authorization rules exist are declared for every request.
  199. For example, an application configuration like:
  200. ====
  201. .Java
  202. [source,java,role="primary"]
  203. ----
  204. @Override
  205. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  206. messages
  207. .simpDestMatchers("/user/queue/errors").permitAll()
  208. .simpDestMatchers("/admin/**").hasRole("ADMIN");
  209. }
  210. ----
  211. .Kotlin
  212. [source,kotlin,role="secondary"]
  213. ----
  214. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  215. messages
  216. .simpDestMatchers("/user/queue/errors").permitAll()
  217. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  218. }
  219. ----
  220. .Xml
  221. [source,xml,role="secondary"]
  222. ----
  223. <websocket-message-broker>
  224. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  225. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  226. </websocket-message-broker>
  227. ----
  228. ====
  229. should change to:
  230. ====
  231. .Java
  232. [source,java,role="primary"]
  233. ----
  234. @Override
  235. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  236. messages
  237. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  238. .simpDestMatchers("/user/queue/errors").permitAll()
  239. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  240. .anyMessage().denyAll();
  241. }
  242. ----
  243. .Kotlin
  244. [source,kotlin,role="secondary"]
  245. ----
  246. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  247. messages
  248. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  249. .simpDestMatchers("/user/queue/errors").permitAll()
  250. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  251. .anyMessage().denyAll()
  252. }
  253. ----
  254. .Xml
  255. [source,xml,role="secondary"]
  256. ----
  257. <websocket-message-broker>
  258. <intercept-message type="CONNECT" access="permitAll"/>
  259. <intercept-message type="DISCONNECT" access="permitAll"/>
  260. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  261. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  262. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  263. <intercept-message pattern="/**" access="denyAll"/>
  264. </websocket-message-broker>
  265. ----
  266. ====
  267. ==== Add `@EnableWebSocketSecurity`
  268. [NOTE]
  269. ====
  270. If you want to have CSRF disabled and you are using Java configuration, the migration steps are slightly different.
  271. Instead of using `@EnableWebSocketSecurity`, you will override the appropriate methods in `WebSocketMessageBrokerConfigurer` yourself.
  272. Please see xref:servlet/integrations/websocket.adoc#websocket-sameorigin-disable[the reference manual] for details about this step.
  273. ====
  274. If you are using Java Configuration, add {security-api-url}org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.html[`@EnableWebSocketSecurity`] to your application.
  275. For example, you can add it to your websocket security configuration class, like so:
  276. ====
  277. .Java
  278. [source,java,role="primary"]
  279. ----
  280. @EnableWebSocketSecurity
  281. @Configuration
  282. public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
  283. // ...
  284. }
  285. ----
  286. .Kotlin
  287. [source,kotlin,role="secondary"]
  288. ----
  289. @EnableWebSocketSecurity
  290. @Configuration
  291. class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
  292. // ...
  293. }
  294. ----
  295. ====
  296. This will make a prototype instance of `MessageMatcherDelegatingAuthorizationManager.Builder` available to encourage configuration by composition instead of extension.
  297. ==== Use an `AuthorizationManager<Message<?>>` instance
  298. To start using `AuthorizationManager`, you can set the `use-authorization-manager` attribute in XML or you can publish an `AuthorizationManager<Message<?>>` `@Bean` in Java.
  299. For example, the following application configuration:
  300. ====
  301. .Java
  302. [source,java,role="primary"]
  303. ----
  304. @Override
  305. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  306. messages
  307. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  308. .simpDestMatchers("/user/queue/errors").permitAll()
  309. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  310. .anyMessage().denyAll();
  311. }
  312. ----
  313. .Kotlin
  314. [source,kotlin,role="secondary"]
  315. ----
  316. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  317. messages
  318. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  319. .simpDestMatchers("/user/queue/errors").permitAll()
  320. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  321. .anyMessage().denyAll()
  322. }
  323. ----
  324. .Xml
  325. [source,xml,role="secondary"]
  326. ----
  327. <websocket-message-broker>
  328. <intercept-message type="CONNECT" access="permitAll"/>
  329. <intercept-message type="DISCONNECT" access="permitAll"/>
  330. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  331. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  332. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  333. <intercept-message pattern="/**" access="denyAll"/>
  334. </websocket-message-broker>
  335. ----
  336. ====
  337. changes to:
  338. ====
  339. .Java
  340. [source,java,role="primary"]
  341. ----
  342. @Bean
  343. AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
  344. messages
  345. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  346. .simpDestMatchers("/user/queue/errors").permitAll()
  347. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  348. .anyMessage().denyAll();
  349. return messages.build();
  350. }
  351. ----
  352. .Kotlin
  353. [source,kotlin,role="secondary"]
  354. ----
  355. @Bean
  356. fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
  357. messages
  358. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  359. .simpDestMatchers("/user/queue/errors").permitAll()
  360. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  361. .anyMessage().denyAll()
  362. return messages.build()
  363. }
  364. ----
  365. .Xml
  366. [source,xml,role="secondary"]
  367. ----
  368. <websocket-message-broker use-authorization-manager="true">
  369. <intercept-message type="CONNECT" access="permitAll"/>
  370. <intercept-message type="DISCONNECT" access="permitAll"/>
  371. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  372. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  373. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  374. <intercept-message pattern="/**" access="denyAll"/>
  375. </websocket-message-broker>
  376. ----
  377. ====
  378. ==== Stop Implementing `AbstractSecurityWebSocketMessageBrokerConfigurer`
  379. If you are using Java configuration, you can now simply extend `WebSocketMessageBrokerConfigurer`.
  380. For example, if your class that extends `AbstractSecurityWebSocketMessageBrokerConfigurer` is called `WebSocketSecurityConfig`, then:
  381. ====
  382. .Java
  383. [source,java,role="primary"]
  384. ----
  385. @EnableWebSocketSecurity
  386. @Configuration
  387. public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
  388. // ...
  389. }
  390. ----
  391. .Kotlin
  392. [source,kotlin,role="secondary"]
  393. ----
  394. @EnableWebSocketSecurity
  395. @Configuration
  396. class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
  397. // ...
  398. }
  399. ----
  400. ====
  401. changes to:
  402. ====
  403. .Java
  404. [source,java,role="primary"]
  405. ----
  406. @EnableWebSocketSecurity
  407. @Configuration
  408. public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
  409. // ...
  410. }
  411. ----
  412. .Kotlin
  413. [source,kotlin,role="secondary"]
  414. ----
  415. @EnableWebSocketSecurity
  416. @Configuration
  417. class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer {
  418. // ...
  419. }
  420. ----
  421. ====
  422. == Reactive
  423. === Use `AuthorizationManager` for Method Security
  424. 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.
  425. '''
  426. 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.
  427. ==== Declare the 5.8 default
  428. First, declare the 5.8 default:
  429. ====
  430. .Java
  431. [source,java,role="primary"]
  432. ----
  433. @EnableReactiveMethodSecurity
  434. ----
  435. .Kotlin
  436. [source,kotlin,role="secondary"]
  437. ----
  438. @EnableReactiveMethodSecurity
  439. ----
  440. ====
  441. to:
  442. ====
  443. .Java
  444. [source,java,role="primary"]
  445. ----
  446. @EnableReactiveMethodSecurity(useAuthorizationManager = false)
  447. ----
  448. .Kotlin
  449. [source,kotlin,role="secondary"]
  450. ----
  451. @EnableReactiveMethodSecurity(useAuthorizationManager = false)
  452. ----
  453. ====
  454. This is helpful because, if the remaining preparation steps cannot be taken, you can still upgrade to 6.0 while keeping this feature as-is.
  455. [[reactive-change-to-useauthorizationmanager]]
  456. ==== Change `useAuthorizationManager` to `true`
  457. To opt in, change `useAuthorizationManager` to `true` like so:
  458. ====
  459. .Java
  460. [source,java,role="primary"]
  461. ----
  462. @EnableReactiveMethodSecurity
  463. ----
  464. .Kotlin
  465. [source,kotlin,role="secondary"]
  466. ----
  467. @EnableReactiveMethodSecurity
  468. ----
  469. ====
  470. changes to:
  471. ====
  472. .Java
  473. [source,java,role="primary"]
  474. ----
  475. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  476. ----
  477. .Kotlin
  478. [source,kotlin,role="secondary"]
  479. ----
  480. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  481. ----
  482. ====
  483. '''
  484. [[reactive-check-for-annotationconfigurationexceptions]]
  485. ==== Check for ``AnnotationConfigurationException``s
  486. `useAuthorizationManager` activates stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
  487. 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.