migration.adoc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  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. ==== Ensure all messages have defined authorization rules
  171. The now-deprecated {security-api-url}org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.html[message security support] permits all messages by default.
  172. xref:servlet/integrations/websocket.adoc[The new support] has the stronger default of denying all messages.
  173. To prepare for this, ensure that authorization rules exist are declared for every request.
  174. For example, an application configuration like:
  175. ====
  176. .Java
  177. [source,java,role="primary"]
  178. ----
  179. @Override
  180. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  181. messages
  182. .simpDestMatchers("/user/queue/errors").permitAll()
  183. .simpDestMatchers("/admin/**").hasRole("ADMIN");
  184. }
  185. ----
  186. .Kotlin
  187. [source,kotlin,role="secondary"]
  188. ----
  189. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  190. messages
  191. .simpDestMatchers("/user/queue/errors").permitAll()
  192. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  193. }
  194. ----
  195. .Xml
  196. [source,xml,role="secondary"]
  197. ----
  198. <websocket-message-broker>
  199. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  200. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  201. </websocket-message-broker>
  202. ----
  203. ====
  204. should change to:
  205. ====
  206. .Java
  207. [source,java,role="primary"]
  208. ----
  209. @Override
  210. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  211. messages
  212. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  213. .simpDestMatchers("/user/queue/errors").permitAll()
  214. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  215. .anyMessage().denyAll();
  216. }
  217. ----
  218. .Kotlin
  219. [source,kotlin,role="secondary"]
  220. ----
  221. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  222. messages
  223. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  224. .simpDestMatchers("/user/queue/errors").permitAll()
  225. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  226. .anyMessage().denyAll()
  227. }
  228. ----
  229. .Xml
  230. [source,xml,role="secondary"]
  231. ----
  232. <websocket-message-broker>
  233. <intercept-message type="CONNECT" access="permitAll"/>
  234. <intercept-message type="DISCONNECT" access="permitAll"/>
  235. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  236. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  237. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  238. <intercept-message pattern="/**" access="denyAll"/>
  239. </websocket-message-broker>
  240. ----
  241. ====
  242. ==== Add `@EnableWebSocketSecurity`
  243. [NOTE]
  244. ====
  245. If you want to have CSRF disabled and you are using Java configuration, the migration steps are slightly different.
  246. Instead of using `@EnableWebSocketSecurity`, you will override the appropriate methods in `WebSocketMessageBrokerConfigurer` yourself.
  247. Please see xref:servlet/integrations/websocket.adoc#websocket-sameorigin-disable[the reference manual] for details about this step.
  248. ====
  249. If you are using Java Configuration, add {security-api-url}org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.html[`@EnableWebSocketSecurity`] to your application.
  250. For example, you can add it to your websocket security configuration class, like so:
  251. ====
  252. .Java
  253. [source,java,role="primary"]
  254. ----
  255. @EnableWebSocketSecurity
  256. @Configuration
  257. public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
  258. // ...
  259. }
  260. ----
  261. .Kotlin
  262. [source,kotlin,role="secondary"]
  263. ----
  264. @EnableWebSocketSecurity
  265. @Configuration
  266. class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
  267. // ...
  268. }
  269. ----
  270. ====
  271. This will make a prototype instance of `MessageMatcherDelegatingAuthorizationManager.Builder` available to encourage configuration by composition instead of extension.
  272. ==== Use an `AuthorizationManager<Message<?>>` instance
  273. To start using `AuthorizationManager`, you can set the `use-authorization-manager` attribute in XML or you can publish an `AuthorizationManager<Message<?>>` `@Bean` in Java.
  274. For example, the following application configuration:
  275. ====
  276. .Java
  277. [source,java,role="primary"]
  278. ----
  279. @Override
  280. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  281. messages
  282. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  283. .simpDestMatchers("/user/queue/errors").permitAll()
  284. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  285. .anyMessage().denyAll();
  286. }
  287. ----
  288. .Kotlin
  289. [source,kotlin,role="secondary"]
  290. ----
  291. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  292. messages
  293. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  294. .simpDestMatchers("/user/queue/errors").permitAll()
  295. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  296. .anyMessage().denyAll()
  297. }
  298. ----
  299. .Xml
  300. [source,xml,role="secondary"]
  301. ----
  302. <websocket-message-broker>
  303. <intercept-message type="CONNECT" access="permitAll"/>
  304. <intercept-message type="DISCONNECT" access="permitAll"/>
  305. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  306. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  307. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  308. <intercept-message pattern="/**" access="denyAll"/>
  309. </websocket-message-broker>
  310. ----
  311. ====
  312. changes to:
  313. ====
  314. .Java
  315. [source,java,role="primary"]
  316. ----
  317. @Bean
  318. AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
  319. messages
  320. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  321. .simpDestMatchers("/user/queue/errors").permitAll()
  322. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  323. .anyMessage().denyAll();
  324. return messages.build();
  325. }
  326. ----
  327. .Kotlin
  328. [source,kotlin,role="secondary"]
  329. ----
  330. @Bean
  331. fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
  332. messages
  333. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  334. .simpDestMatchers("/user/queue/errors").permitAll()
  335. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  336. .anyMessage().denyAll()
  337. return messages.build()
  338. }
  339. ----
  340. .Xml
  341. [source,xml,role="secondary"]
  342. ----
  343. <websocket-message-broker use-authorization-manager="true">
  344. <intercept-message type="CONNECT" access="permitAll"/>
  345. <intercept-message type="DISCONNECT" access="permitAll"/>
  346. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  347. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  348. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  349. <intercept-message pattern="/**" access="denyAll"/>
  350. </websocket-message-broker>
  351. ----
  352. ====
  353. ==== Stop Implementing `AbstractSecurityWebSocketMessageBrokerConfigurer`
  354. If you are using Java configuration, you can now simply extend `WebSocketMessageBrokerConfigurer`.
  355. For example, if your class that extends `AbstractSecurityWebSocketMessageBrokerConfigurer` is called `WebSocketSecurityConfig`, then:
  356. ====
  357. .Java
  358. [source,java,role="primary"]
  359. ----
  360. @EnableWebSocketSecurity
  361. @Configuration
  362. public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
  363. // ...
  364. }
  365. ----
  366. .Kotlin
  367. [source,kotlin,role="secondary"]
  368. ----
  369. @EnableWebSocketSecurity
  370. @Configuration
  371. class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
  372. // ...
  373. }
  374. ----
  375. ====
  376. changes to:
  377. ====
  378. .Java
  379. [source,java,role="primary"]
  380. ----
  381. @EnableWebSocketSecurity
  382. @Configuration
  383. public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
  384. // ...
  385. }
  386. ----
  387. .Kotlin
  388. [source,kotlin,role="secondary"]
  389. ----
  390. @EnableWebSocketSecurity
  391. @Configuration
  392. class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer {
  393. // ...
  394. }
  395. ----
  396. ====
  397. == Reactive
  398. === Use `AuthorizationManager` for Method Security
  399. 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.
  400. '''
  401. [[reactive-change-to-useauthorizationmanager]]
  402. ==== Change `useAuthorizationManager` to `true`
  403. 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.
  404. To opt in, change `useAuthorizationManager` to `true` like so:
  405. ====
  406. .Java
  407. [source,java,role="primary"]
  408. ----
  409. @EnableReactiveMethodSecurity
  410. ----
  411. .Kotlin
  412. [source,kotlin,role="secondary"]
  413. ----
  414. @EnableReactiveMethodSecurity
  415. ----
  416. ====
  417. changes to:
  418. ====
  419. .Java
  420. [source,java,role="primary"]
  421. ----
  422. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  423. ----
  424. .Kotlin
  425. [source,kotlin,role="secondary"]
  426. ----
  427. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  428. ----
  429. ====
  430. [NOTE]
  431. =====
  432. In 6.0, `useAuthorizationManager` defaults to `true`.
  433. =====
  434. '''
  435. [[reactive-check-for-annotationconfigurationexceptions]]
  436. ==== Check for ``AnnotationConfigurationException``s
  437. `useAuthorizationManager` activates stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
  438. 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.