migration.adoc 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  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. === Use `AuthorizationManager` for Request Security
  423. xref:servlet/authorization/authorize-requests.adoc[HTTP Request Security] has been xref:servlet/authorization/authorize-http-requests.adoc[simplified] through {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[the `AuthorizationManager` API].
  424. ==== Declare the 5.8 default
  425. Spring Security 5.8 and earlier only xref:servlet/authorization/architecture.adoc[perform authorization] once per request.
  426. This means that dispatcher types like `FORWARD` and `INCLUDE` that run after `REQUEST` are not secured by default.
  427. It's recommended that Spring Security secure all dispatch types.
  428. As such, in 6.0, Spring Security changes this default.
  429. In case you have trouble with the remaining steps and cannot use `AuthorizationManager` at this time, it's recommended as a first step to declare the 5.8 default so that the 5.8 behavior is preserved when you update.
  430. To declare the 5.8 default, change:
  431. ====
  432. .Java
  433. [source,java,role="primary"]
  434. ----
  435. http
  436. .authorizeRequests((authorize) -> authorize
  437. .mvcMatchers("/app/**").hasRole("APP")
  438. // ...
  439. )
  440. // ...
  441. ----
  442. .Kotlin
  443. [source,kotlin,role="secondary"]
  444. ----
  445. http {
  446. authorizeRequests {
  447. authorize("/messages/**", hasRole("APP"))
  448. // ...
  449. }
  450. }
  451. ----
  452. .Xml
  453. [source,xml,role="secondary"]
  454. ----
  455. <http>
  456. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  457. <!-- ... -->
  458. </http>
  459. ----
  460. ====
  461. to:
  462. ====
  463. .Java
  464. [source,java,role="primary"]
  465. ----
  466. http
  467. .authorizeRequests((authorize) -> authorize
  468. .filterSecurityInterceptorOncePerRequest(true)
  469. .mvcMatchers("/app/**").hasRole("APP")
  470. // ...
  471. )
  472. // ...
  473. ----
  474. .Kotlin
  475. [source,kotlin,role="secondary"]
  476. ----
  477. http {
  478. authorizeRequests {
  479. filterSecurityInterceptorOncePerRequest = true
  480. authorize("/messages/**", hasRole("APP"))
  481. // ...
  482. }
  483. }
  484. ----
  485. .Xml
  486. [source,xml,role="secondary"]
  487. ----
  488. <http once-per-request="true" use-authorization-manager="false">
  489. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  490. <!-- ... -->
  491. </http>
  492. ----
  493. ====
  494. Or, if you already migrated to `authorizeHttpRequests` in a previous release, change:
  495. ====
  496. .Java
  497. [source,java,role="primary"]
  498. ----
  499. http
  500. .authorizeHttpRequests((authorize) -> authorize
  501. .mvcMatchers("/app/**").hasRole("APP")
  502. // ...
  503. )
  504. // ...
  505. ----
  506. .Kotlin
  507. [source,kotlin,role="secondary"]
  508. ----
  509. http {
  510. authorizeHttpRequests {
  511. authorize("/messages/**", hasRole("APP"))
  512. // ...
  513. }
  514. }
  515. ----
  516. ====
  517. to:
  518. ====
  519. .Java
  520. [source,java,role="primary"]
  521. ----
  522. http
  523. .authorizeHttpRequests((authorize) -> authorize
  524. .shouldFilterAllDispatcherTypes(false)
  525. .mvcMatchers("/app/**").hasRole("APP")
  526. // ...
  527. )
  528. // ...
  529. ----
  530. .Kotlin
  531. [source,kotlin,role="secondary"]
  532. ----
  533. http {
  534. authorizeHttpRequests {
  535. shouldFilterAllDispatcherTypes = false
  536. authorize("/messages/**", hasRole("APP"))
  537. // ...
  538. }
  539. }
  540. ----
  541. ====
  542. This value will be switched in a later step, but now you are ready for upgrading in case you run into trouble with the remaining steps.
  543. ==== Ensure that all requests have defined authorization rules
  544. In Spring Security 5.8 and earlier, requests with no authorization rule are permitted by default.
  545. It is a stronger security position to deny by default, thus requiring that authorization rules be clearly defined for every endpoint.
  546. As such, in 6.0, Spring Security by default denies any request that is missing an authorization rule.
  547. The simplest way to prepare for this change is to introduce an appropriate {security-api-url}org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistry.html#anyRequest()[`anyRequest`] rule as the last authorization rule.
  548. The recommendation is {security-api-url}org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.AuthorizedUrl.html#denyAll()[`denyAll`] since that is the implied 6.0 default; however, you may want to choose {security-api-url}org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.AuthorizedUrl.html#permitAll()[`permitAll`] to preserve 5.8 behavior.
  549. [NOTE]
  550. ====
  551. You may already have an `anyRequest` rule defined that you are happy with in which case this step can be skipped.
  552. ====
  553. Adding `denyAll` to the end looks like changing:
  554. ====
  555. .Java
  556. [source,java,role="primary"]
  557. ----
  558. http
  559. .authorizeRequests((authorize) -> authorize
  560. .filterSecurityInterceptorOncePerRequest(true)
  561. .mvcMatchers("/app/**").hasRole("APP")
  562. // ...
  563. )
  564. // ...
  565. ----
  566. .Kotlin
  567. [source,kotlin,role="secondary"]
  568. ----
  569. http {
  570. authorizeRequests {
  571. filterSecurityInterceptorOncePerRequest = true
  572. authorize("/app/**", hasRole("APP"))
  573. // ...
  574. }
  575. }
  576. ----
  577. .Xml
  578. [source,xml,role="secondary"]
  579. ----
  580. <http once-per-request="true">
  581. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  582. <!-- ... -->
  583. </http>
  584. ----
  585. ====
  586. to:
  587. ====
  588. .Java
  589. [source,java,role="primary"]
  590. ----
  591. http
  592. .authorizeRequests((authorize) -> authorize
  593. .filterSecurityInterceptorOncePerRequest(true)
  594. .mvcMatchers("/app/**").hasRole("APP")
  595. // ...
  596. .anyRequest().denyAll()
  597. )
  598. // ...
  599. ----
  600. .Kotlin
  601. [source,kotlin,role="secondary"]
  602. ----
  603. http {
  604. authorizeRequests {
  605. filterSecurityInterceptorOncePerRequest = true
  606. authorize("/app/**", hasRole("APP"))
  607. // ...
  608. authorize(anyRequest, denyAll)
  609. }
  610. }
  611. ----
  612. .Xml
  613. [source,xml,role="secondary"]
  614. ----
  615. <http once-per-request="true">
  616. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  617. <!-- ... -->
  618. <intercept-url pattern="/**" access="denyAll"/>
  619. </http>
  620. ----
  621. ====
  622. If you have already migrated to `authorizeHttpRequests`, the recommended change is the same.
  623. ==== Switch to `AuthorizationManager`
  624. To opt in to using `AuthorizationManager`, you can use `authorizeHttpRequests` or xref:servlet/appendix/namespace/http.adoc#nsa-http-use-authorization-manager[`use-authorization-manager`] for Java or XML, respectively.
  625. Change:
  626. ====
  627. .Java
  628. [source,java,role="primary"]
  629. ----
  630. http
  631. .authorizeRequests((authorize) -> authorize
  632. .filterSecurityInterceptorOncePerRequest(true)
  633. .mvcMatchers("/app/**").hasRole("APP")
  634. // ...
  635. .anyRequest().denyAll()
  636. )
  637. // ...
  638. ----
  639. .Kotlin
  640. [source,kotlin,role="secondary"]
  641. ----
  642. http {
  643. authorizeRequests {
  644. filterSecurityInterceptorOncePerRequest = true
  645. authorize("/app/**", hasRole("APP"))
  646. // ...
  647. authorize(anyRequest, denyAll)
  648. }
  649. }
  650. ----
  651. .Xml
  652. [source,xml,role="secondary"]
  653. ----
  654. <http once-per-request="true">
  655. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  656. <!-- ... -->
  657. <intercept-url pattern="/**" access="denyAll"/>
  658. </http>
  659. ----
  660. ====
  661. to:
  662. ====
  663. .Java
  664. [source,java,role="primary"]
  665. ----
  666. http
  667. .authorizeHttpRequests((authorize) -> authorize
  668. .shouldFilterAllDispatcherTypes(false)
  669. .mvcMatchers("/app/**").hasRole("APP")
  670. // ...
  671. .anyRequest().denyAll()
  672. )
  673. // ...
  674. ----
  675. .Kotlin
  676. [source,kotlin,role="secondary"]
  677. ----
  678. http {
  679. authorizeHttpRequests {
  680. shouldFilterAllDispatcherTypes = false
  681. authorize("/app/**", hasRole("APP"))
  682. // ...
  683. authorize(anyRequest, denyAll)
  684. }
  685. }
  686. ----
  687. .Xml
  688. [source,xml,role="secondary"]
  689. ----
  690. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  691. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  692. <!-- ... -->
  693. <intercept-url pattern="/**" access="denyAll"/>
  694. </http>
  695. ----
  696. ====
  697. ==== Migrate SpEL expressions to `AuthorizationManager`
  698. For authorization rules, Java tends to be easier to test and maintain than SpEL.
  699. As such, `authorizeHttpRequests` does not have a method for declaring a `String` SpEL.
  700. Instead, you can implement your own `AuthorizationManager` implementation or use `WebExpressionAuthorizationManager`.
  701. For completeness, both options will be demonstrated.
  702. First, if you have the following SpEL:
  703. ====
  704. .Java
  705. [source,java,role="primary"]
  706. ----
  707. http
  708. .authorizeRequests((authorize) -> authorize
  709. .filterSecurityInterceptorOncePerRequest(true)
  710. .mvcMatchers("/complicated/**").access("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
  711. // ...
  712. .anyRequest().denyAll()
  713. )
  714. // ...
  715. ----
  716. .Kotlin
  717. [source,kotlin,role="secondary"]
  718. ----
  719. http {
  720. authorizeRequests {
  721. filterSecurityInterceptorOncePerRequest = true
  722. authorize("/complicated/**", access("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
  723. // ...
  724. authorize(anyRequest, denyAll)
  725. }
  726. }
  727. ----
  728. ====
  729. Then you can compose your own `AuthorizationManager` with Spring Security authorization primitives like so:
  730. ====
  731. .Java
  732. [source,java,role="primary"]
  733. ----
  734. http
  735. .authorizeHttpRequests((authorize) -> authorize
  736. .shouldFilterAllDispatcherTypes(false)
  737. .mvcMatchers("/complicated/**").access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
  738. // ...
  739. .anyRequest().denyAll()
  740. )
  741. // ...
  742. ----
  743. .Kotlin
  744. [source,kotlin,role="secondary"]
  745. ----
  746. http {
  747. authorizeHttpRequests {
  748. shouldFilterAllDispatcherTypes = false
  749. authorize("/complicated/**", access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
  750. // ...
  751. authorize(anyRequest, denyAll)
  752. }
  753. }
  754. ----
  755. ====
  756. Or you can use `WebExpressionAuthorizationManager` in the following way:
  757. ====
  758. .Java
  759. [source,java,role="primary"]
  760. ----
  761. http
  762. .authorizeRequests((authorize) -> authorize
  763. .filterSecurityInterceptorOncePerRequest(true)
  764. .mvcMatchers("/complicated/**").access(
  765. new WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
  766. )
  767. // ...
  768. .anyRequest().denyAll()
  769. )
  770. // ...
  771. ----
  772. .Kotlin
  773. [source,kotlin,role="secondary"]
  774. ----
  775. http {
  776. authorizeRequests {
  777. filterSecurityInterceptorOncePerRequest = true
  778. authorize("/complicated/**", access(
  779. WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
  780. )
  781. // ...
  782. authorize(anyRequest, denyAll)
  783. }
  784. }
  785. ----
  786. ====
  787. ==== Switch to filter all dispatcher types
  788. Finally, change your authorization rules to filter all dispatcher types.
  789. To do this, change:
  790. ====
  791. .Java
  792. [source,java,role="primary"]
  793. ----
  794. http
  795. .authorizeHttpRequests((authorize) -> authorize
  796. .shouldFilterAllDispatcherTypes(false)
  797. .mvcMatchers("/app/**").hasRole("APP")
  798. // ...
  799. .anyRequest().denyAll()
  800. )
  801. // ...
  802. ----
  803. .Kotlin
  804. [source,kotlin,role="secondary"]
  805. ----
  806. http {
  807. authorizeHttpRequests {
  808. shouldFilterAllDispatcherTypes = false
  809. authorize("/app/**", hasRole("APP"))
  810. // ...
  811. authorize(anyRequest, denyAll)
  812. }
  813. }
  814. ----
  815. .Xml
  816. [source,xml,role="secondary"]
  817. ----
  818. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  819. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  820. <!-- ... -->
  821. <intercept-url pattern="/**" access="denyAll"/>
  822. </http>
  823. ----
  824. ====
  825. to:
  826. ====
  827. .Java
  828. [source,java,role="primary"]
  829. ----
  830. http
  831. .authorizeHttpRequests((authorize) -> authorize
  832. .shouldFilterAllDispatcherTypes(true)
  833. .mvcMatchers("/app/**").hasRole("APP")
  834. // ...
  835. .anyRequest().denyAll()
  836. )
  837. // ...
  838. ----
  839. .Kotlin
  840. [source,kotlin,role="secondary"]
  841. ----
  842. http {
  843. authorizeHttpRequests {
  844. shouldFilterAllDispatcherTypes = true
  845. authorize("/app/**", hasRole("APP"))
  846. // ...
  847. authorize(anyRequest, denyAll)
  848. }
  849. }
  850. ----
  851. .Xml
  852. [source,xml,role="secondary"]
  853. ----
  854. <http filter-all-dispatcher-types="true" use-authorization-manager="true">
  855. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  856. <!-- ... -->
  857. <intercept-url pattern="/**" access="denyAll"/>
  858. </http>
  859. ----
  860. ====
  861. == Reactive
  862. === Use `AuthorizationManager` for Method Security
  863. 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.
  864. '''
  865. 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.
  866. ==== Declare the 5.8 default
  867. First, declare the 5.8 default:
  868. ====
  869. .Java
  870. [source,java,role="primary"]
  871. ----
  872. @EnableReactiveMethodSecurity
  873. ----
  874. .Kotlin
  875. [source,kotlin,role="secondary"]
  876. ----
  877. @EnableReactiveMethodSecurity
  878. ----
  879. ====
  880. to:
  881. ====
  882. .Java
  883. [source,java,role="primary"]
  884. ----
  885. @EnableReactiveMethodSecurity(useAuthorizationManager = false)
  886. ----
  887. .Kotlin
  888. [source,kotlin,role="secondary"]
  889. ----
  890. @EnableReactiveMethodSecurity(useAuthorizationManager = false)
  891. ----
  892. ====
  893. 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.
  894. [[reactive-change-to-useauthorizationmanager]]
  895. ==== Change `useAuthorizationManager` to `true`
  896. To opt in, change `useAuthorizationManager` to `true` like so:
  897. ====
  898. .Java
  899. [source,java,role="primary"]
  900. ----
  901. @EnableReactiveMethodSecurity
  902. ----
  903. .Kotlin
  904. [source,kotlin,role="secondary"]
  905. ----
  906. @EnableReactiveMethodSecurity
  907. ----
  908. ====
  909. changes to:
  910. ====
  911. .Java
  912. [source,java,role="primary"]
  913. ----
  914. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  915. ----
  916. .Kotlin
  917. [source,kotlin,role="secondary"]
  918. ----
  919. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  920. ----
  921. ====
  922. '''
  923. [[reactive-check-for-annotationconfigurationexceptions]]
  924. ==== Check for ``AnnotationConfigurationException``s
  925. `useAuthorizationManager` activates stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
  926. 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.