migration.adoc 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
  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. Should you run into trouble with making these changes, note that `@EnableGlobalMethodSecurity`, while deprecated, will not be removed in 6.0, allowing you to opt out by sticking with the old annotation.
  35. '''
  36. [[servlet-replace-globalmethodsecurity-with-methodsecurity]]
  37. ==== 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]
  38. {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.
  39. 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.
  40. This means that the following two listings are functionally equivalent:
  41. ====
  42. .Java
  43. [source,java,role="primary"]
  44. ----
  45. @EnableGlobalMethodSecurity(prePostEnabled = true)
  46. ----
  47. .Kotlin
  48. [source,kotlin,role="secondary"]
  49. ----
  50. @EnableGlobalMethodSecurity(prePostEnabled = true)
  51. ----
  52. .Xml
  53. [source,xml,role="secondary"]
  54. ----
  55. <global-method-security pre-post-enabled="true"/>
  56. ----
  57. ====
  58. and:
  59. ====
  60. .Java
  61. [source,java,role="primary"]
  62. ----
  63. @EnableMethodSecurity
  64. ----
  65. .Kotlin
  66. [source,kotlin,role="secondary"]
  67. ----
  68. @EnableMethodSecurity
  69. ----
  70. .Xml
  71. [source,xml,role="secondary"]
  72. ----
  73. <method-security/>
  74. ----
  75. ====
  76. For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior.
  77. For example, a listing like:
  78. ====
  79. .Java
  80. [source,java,role="primary"]
  81. ----
  82. @EnableGlobalMethodSecurity(securedEnabled = true)
  83. ----
  84. .Kotlin
  85. [source,kotlin,role="secondary"]
  86. ----
  87. @EnableGlobalMethodSecurity(securedEnabled = true)
  88. ----
  89. .Xml
  90. [source,xml,role="secondary"]
  91. ----
  92. <global-method-security secured-enabled="true"/>
  93. ----
  94. ====
  95. should change to:
  96. ====
  97. .Java
  98. [source,java,role="primary"]
  99. ----
  100. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  101. ----
  102. .Kotlin
  103. [source,kotlin,role="secondary"]
  104. ----
  105. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  106. ----
  107. .Xml
  108. [source,xml,role="secondary"]
  109. ----
  110. <method-security secured-enabled="true" pre-post-enabled="false"/>
  111. ----
  112. ====
  113. '''
  114. [[servlet-replace-permissionevaluator-bean-with-methodsecurityexpression-handler]]
  115. ==== Publish a `MethodSecurityExpressionHandler` instead of a `PermissionEvaluator`
  116. `@EnableMethodSecurity` does not pick up a `PermissionEvaluator`.
  117. This helps keep its API simple.
  118. If you have a custom {security-api-url}org/springframework/security/access/PermissionEvaluator.html[`PermissionEvaluator`] `@Bean`, please change it from:
  119. ====
  120. .Java
  121. [source,java,role="primary"]
  122. ----
  123. @Bean
  124. static PermissionEvaluator permissionEvaluator() {
  125. // ... your evaluator
  126. }
  127. ----
  128. .Kotlin
  129. [source,kotlin,role="secondary"]
  130. ----
  131. companion object {
  132. @Bean
  133. fun permissionEvaluator(): PermissionEvaluator {
  134. // ... your evaluator
  135. }
  136. }
  137. ----
  138. ====
  139. to:
  140. ====
  141. .Java
  142. [source,java,role="primary"]
  143. ----
  144. @Bean
  145. static MethodSecurityExpressionHandler expressionHandler() {
  146. var expressionHandler = new DefaultMethodSecurityExpressionHandler();
  147. expressionHandler.setPermissionEvaluator(myPermissionEvaluator);
  148. return expressionHandler;
  149. }
  150. ----
  151. .Kotlin
  152. [source,kotlin,role="secondary"]
  153. ----
  154. companion object {
  155. @Bean
  156. fun expressionHandler(): MethodSecurityExpressionHandler {
  157. val expressionHandler = DefaultMethodSecurityExpressionHandler
  158. expressionHandler.setPermissionEvaluator(myPermissionEvaluator)
  159. return expressionHandler
  160. }
  161. }
  162. ----
  163. ====
  164. '''
  165. [[servlet-check-for-annotationconfigurationexceptions]]
  166. ==== Check for ``AnnotationConfigurationException``s
  167. `@EnableMethodSecurity` and `<method-security>` activate stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
  168. 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.
  169. === Use `AuthorizationManager` for Message Security
  170. 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.
  171. Should you run into trouble with making these changes, you can follow the <<servlet-authorizationmanager-messages-opt-out,opt out steps>> at the end of this section.
  172. ==== Ensure all messages have defined authorization rules
  173. The now-deprecated {security-api-url}org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.html[message security support] permits all messages by default.
  174. xref:servlet/integrations/websocket.adoc[The new support] has the stronger default of denying all messages.
  175. To prepare for this, ensure that authorization rules exist are declared for every request.
  176. For example, an application configuration like:
  177. ====
  178. .Java
  179. [source,java,role="primary"]
  180. ----
  181. @Override
  182. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  183. messages
  184. .simpDestMatchers("/user/queue/errors").permitAll()
  185. .simpDestMatchers("/admin/**").hasRole("ADMIN");
  186. }
  187. ----
  188. .Kotlin
  189. [source,kotlin,role="secondary"]
  190. ----
  191. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  192. messages
  193. .simpDestMatchers("/user/queue/errors").permitAll()
  194. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  195. }
  196. ----
  197. .Xml
  198. [source,xml,role="secondary"]
  199. ----
  200. <websocket-message-broker>
  201. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  202. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  203. </websocket-message-broker>
  204. ----
  205. ====
  206. should change to:
  207. ====
  208. .Java
  209. [source,java,role="primary"]
  210. ----
  211. @Override
  212. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  213. messages
  214. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  215. .simpDestMatchers("/user/queue/errors").permitAll()
  216. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  217. .anyMessage().denyAll();
  218. }
  219. ----
  220. .Kotlin
  221. [source,kotlin,role="secondary"]
  222. ----
  223. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  224. messages
  225. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  226. .simpDestMatchers("/user/queue/errors").permitAll()
  227. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  228. .anyMessage().denyAll()
  229. }
  230. ----
  231. .Xml
  232. [source,xml,role="secondary"]
  233. ----
  234. <websocket-message-broker>
  235. <intercept-message type="CONNECT" access="permitAll"/>
  236. <intercept-message type="DISCONNECT" access="permitAll"/>
  237. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  238. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  239. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  240. <intercept-message pattern="/**" access="denyAll"/>
  241. </websocket-message-broker>
  242. ----
  243. ====
  244. ==== Add `@EnableWebSocketSecurity`
  245. [NOTE]
  246. ====
  247. If you want to have CSRF disabled and you are using Java configuration, the migration steps are slightly different.
  248. Instead of using `@EnableWebSocketSecurity`, you will override the appropriate methods in `WebSocketMessageBrokerConfigurer` yourself.
  249. Please see xref:servlet/integrations/websocket.adoc#websocket-sameorigin-disable[the reference manual] for details about this step.
  250. ====
  251. If you are using Java Configuration, add {security-api-url}org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.html[`@EnableWebSocketSecurity`] to your application.
  252. For example, you can add it to your websocket security configuration class, like so:
  253. ====
  254. .Java
  255. [source,java,role="primary"]
  256. ----
  257. @EnableWebSocketSecurity
  258. @Configuration
  259. public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
  260. // ...
  261. }
  262. ----
  263. .Kotlin
  264. [source,kotlin,role="secondary"]
  265. ----
  266. @EnableWebSocketSecurity
  267. @Configuration
  268. class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
  269. // ...
  270. }
  271. ----
  272. ====
  273. This will make a prototype instance of `MessageMatcherDelegatingAuthorizationManager.Builder` available to encourage configuration by composition instead of extension.
  274. ==== Use an `AuthorizationManager<Message<?>>` instance
  275. To start using `AuthorizationManager`, you can set the `use-authorization-manager` attribute in XML or you can publish an `AuthorizationManager<Message<?>>` `@Bean` in Java.
  276. For example, the following application configuration:
  277. ====
  278. .Java
  279. [source,java,role="primary"]
  280. ----
  281. @Override
  282. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  283. messages
  284. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  285. .simpDestMatchers("/user/queue/errors").permitAll()
  286. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  287. .anyMessage().denyAll();
  288. }
  289. ----
  290. .Kotlin
  291. [source,kotlin,role="secondary"]
  292. ----
  293. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  294. messages
  295. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  296. .simpDestMatchers("/user/queue/errors").permitAll()
  297. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  298. .anyMessage().denyAll()
  299. }
  300. ----
  301. .Xml
  302. [source,xml,role="secondary"]
  303. ----
  304. <websocket-message-broker>
  305. <intercept-message type="CONNECT" access="permitAll"/>
  306. <intercept-message type="DISCONNECT" access="permitAll"/>
  307. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  308. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  309. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  310. <intercept-message pattern="/**" access="denyAll"/>
  311. </websocket-message-broker>
  312. ----
  313. ====
  314. changes to:
  315. ====
  316. .Java
  317. [source,java,role="primary"]
  318. ----
  319. @Bean
  320. AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
  321. messages
  322. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  323. .simpDestMatchers("/user/queue/errors").permitAll()
  324. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  325. .anyMessage().denyAll();
  326. return messages.build();
  327. }
  328. ----
  329. .Kotlin
  330. [source,kotlin,role="secondary"]
  331. ----
  332. @Bean
  333. fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
  334. messages
  335. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  336. .simpDestMatchers("/user/queue/errors").permitAll()
  337. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  338. .anyMessage().denyAll()
  339. return messages.build()
  340. }
  341. ----
  342. .Xml
  343. [source,xml,role="secondary"]
  344. ----
  345. <websocket-message-broker use-authorization-manager="true">
  346. <intercept-message type="CONNECT" access="permitAll"/>
  347. <intercept-message type="DISCONNECT" access="permitAll"/>
  348. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  349. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  350. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  351. <intercept-message pattern="/**" access="denyAll"/>
  352. </websocket-message-broker>
  353. ----
  354. ====
  355. ==== Stop Implementing `AbstractSecurityWebSocketMessageBrokerConfigurer`
  356. If you are using Java configuration, you can now simply extend `WebSocketMessageBrokerConfigurer`.
  357. For example, if your class that extends `AbstractSecurityWebSocketMessageBrokerConfigurer` is called `WebSocketSecurityConfig`, then:
  358. ====
  359. .Java
  360. [source,java,role="primary"]
  361. ----
  362. @EnableWebSocketSecurity
  363. @Configuration
  364. public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
  365. // ...
  366. }
  367. ----
  368. .Kotlin
  369. [source,kotlin,role="secondary"]
  370. ----
  371. @EnableWebSocketSecurity
  372. @Configuration
  373. class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
  374. // ...
  375. }
  376. ----
  377. ====
  378. changes to:
  379. ====
  380. .Java
  381. [source,java,role="primary"]
  382. ----
  383. @EnableWebSocketSecurity
  384. @Configuration
  385. public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
  386. // ...
  387. }
  388. ----
  389. .Kotlin
  390. [source,kotlin,role="secondary"]
  391. ----
  392. @EnableWebSocketSecurity
  393. @Configuration
  394. class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer {
  395. // ...
  396. }
  397. ----
  398. ====
  399. [[servlet-authorizationmanager-messages-opt-out]]
  400. ==== Opt-out Steps
  401. In case you had trouble, take a look at these scenarios for optimal opt out behavior:
  402. ===== I cannot declare an authorization rule for all requests
  403. If you are having trouble setting an `anyRequest` authorization rule of `denyAll`, please use {security-api-url}org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.Builder.Constraint.html#permitAll()[`permitAll`] instead, like so:
  404. ====
  405. .Java
  406. [source,java,role="primary"]
  407. ----
  408. @Bean
  409. AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
  410. messages
  411. .simpDestMatchers("/user/queue/errors").permitAll()
  412. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  413. // ...
  414. .anyMessage().permitAll();
  415. return messages.build();
  416. }
  417. ----
  418. .Kotlin
  419. [source,kotlin,role="secondary"]
  420. ----
  421. @Bean
  422. fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
  423. messages
  424. .simpDestMatchers("/user/queue/errors").permitAll()
  425. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  426. // ...
  427. .anyMessage().permitAll();
  428. return messages.build()
  429. }
  430. ----
  431. .Xml
  432. [source,xml,role="secondary"]
  433. ----
  434. <websocket-message-broker use-authorization-manager="true">
  435. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  436. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  437. <!-- ... -->
  438. <intercept-message pattern="/**" access="permitAll"/>
  439. </websocket-message-broker>
  440. ----
  441. ====
  442. ===== I cannot get CSRF working, need some other `AbstractSecurityWebSocketMessageBrokerConfigurer` feature, or am having trouble with `AuthorizationManager`
  443. In the case of Java, you may continue using `AbstractMessageSecurityWebSocketMessageBrokerConfigurer`.
  444. Even though it is deprecated, it will not be removed in 6.0.
  445. In the case of XML, you can opt out of `AuthorizationManager` by setting `use-authorization-manager="false"`:
  446. ====
  447. .Xml
  448. [source,xml,role="secondary"]
  449. ----
  450. <websocket-message-broker>
  451. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  452. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  453. </websocket-message-broker>
  454. ----
  455. ====
  456. to:
  457. ====
  458. .Xml
  459. [source,xml,role="secondary"]
  460. ----
  461. <websocket-message-broker use-authorization-manager="false">
  462. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  463. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  464. </websocket-message-broker>
  465. ----
  466. ====
  467. === Use `AuthorizationManager` for Request Security
  468. 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].
  469. Should you run into trouble with making these changes, you can follow the <<servlet-authorizationmanager-requests-opt-out,opt out steps>> at the end of this section.
  470. ==== Ensure that all requests have defined authorization rules
  471. In Spring Security 5.8 and earlier, requests with no authorization rule are permitted by default.
  472. It is a stronger security position to deny by default, thus requiring that authorization rules be clearly defined for every endpoint.
  473. As such, in 6.0, Spring Security by default denies any request that is missing an authorization rule.
  474. 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.
  475. 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.
  476. [NOTE]
  477. ====
  478. You may already have an `anyRequest` rule defined that you are happy with in which case this step can be skipped.
  479. ====
  480. Adding `denyAll` to the end looks like changing:
  481. ====
  482. .Java
  483. [source,java,role="primary"]
  484. ----
  485. http
  486. .authorizeRequests((authorize) -> authorize
  487. .filterSecurityInterceptorOncePerRequest(true)
  488. .mvcMatchers("/app/**").hasRole("APP")
  489. // ...
  490. )
  491. // ...
  492. ----
  493. .Kotlin
  494. [source,kotlin,role="secondary"]
  495. ----
  496. http {
  497. authorizeRequests {
  498. filterSecurityInterceptorOncePerRequest = true
  499. authorize("/app/**", hasRole("APP"))
  500. // ...
  501. }
  502. }
  503. ----
  504. .Xml
  505. [source,xml,role="secondary"]
  506. ----
  507. <http once-per-request="true">
  508. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  509. <!-- ... -->
  510. </http>
  511. ----
  512. ====
  513. to:
  514. ====
  515. .Java
  516. [source,java,role="primary"]
  517. ----
  518. http
  519. .authorizeRequests((authorize) -> authorize
  520. .filterSecurityInterceptorOncePerRequest(true)
  521. .mvcMatchers("/app/**").hasRole("APP")
  522. // ...
  523. .anyRequest().denyAll()
  524. )
  525. // ...
  526. ----
  527. .Kotlin
  528. [source,kotlin,role="secondary"]
  529. ----
  530. http {
  531. authorizeRequests {
  532. filterSecurityInterceptorOncePerRequest = true
  533. authorize("/app/**", hasRole("APP"))
  534. // ...
  535. authorize(anyRequest, denyAll)
  536. }
  537. }
  538. ----
  539. .Xml
  540. [source,xml,role="secondary"]
  541. ----
  542. <http once-per-request="true">
  543. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  544. <!-- ... -->
  545. <intercept-url pattern="/**" access="denyAll"/>
  546. </http>
  547. ----
  548. ====
  549. If you have already migrated to `authorizeHttpRequests`, the recommended change is the same.
  550. ==== Switch to `AuthorizationManager`
  551. 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.
  552. Change:
  553. ====
  554. .Java
  555. [source,java,role="primary"]
  556. ----
  557. http
  558. .authorizeRequests((authorize) -> authorize
  559. .filterSecurityInterceptorOncePerRequest(true)
  560. .mvcMatchers("/app/**").hasRole("APP")
  561. // ...
  562. .anyRequest().denyAll()
  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. authorize(anyRequest, denyAll)
  575. }
  576. }
  577. ----
  578. .Xml
  579. [source,xml,role="secondary"]
  580. ----
  581. <http once-per-request="true">
  582. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  583. <!-- ... -->
  584. <intercept-url pattern="/**" access="denyAll"/>
  585. </http>
  586. ----
  587. ====
  588. to:
  589. ====
  590. .Java
  591. [source,java,role="primary"]
  592. ----
  593. http
  594. .authorizeHttpRequests((authorize) -> authorize
  595. .shouldFilterAllDispatcherTypes(false)
  596. .mvcMatchers("/app/**").hasRole("APP")
  597. // ...
  598. .anyRequest().denyAll()
  599. )
  600. // ...
  601. ----
  602. .Kotlin
  603. [source,kotlin,role="secondary"]
  604. ----
  605. http {
  606. authorizeHttpRequests {
  607. shouldFilterAllDispatcherTypes = false
  608. authorize("/app/**", hasRole("APP"))
  609. // ...
  610. authorize(anyRequest, denyAll)
  611. }
  612. }
  613. ----
  614. .Xml
  615. [source,xml,role="secondary"]
  616. ----
  617. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  618. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  619. <!-- ... -->
  620. <intercept-url pattern="/**" access="denyAll"/>
  621. </http>
  622. ----
  623. ====
  624. ==== Migrate SpEL expressions to `AuthorizationManager`
  625. For authorization rules, Java tends to be easier to test and maintain than SpEL.
  626. As such, `authorizeHttpRequests` does not have a method for declaring a `String` SpEL.
  627. Instead, you can implement your own `AuthorizationManager` implementation or use `WebExpressionAuthorizationManager`.
  628. For completeness, both options will be demonstrated.
  629. First, if you have the following SpEL:
  630. ====
  631. .Java
  632. [source,java,role="primary"]
  633. ----
  634. http
  635. .authorizeRequests((authorize) -> authorize
  636. .filterSecurityInterceptorOncePerRequest(true)
  637. .mvcMatchers("/complicated/**").access("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
  638. // ...
  639. .anyRequest().denyAll()
  640. )
  641. // ...
  642. ----
  643. .Kotlin
  644. [source,kotlin,role="secondary"]
  645. ----
  646. http {
  647. authorizeRequests {
  648. filterSecurityInterceptorOncePerRequest = true
  649. authorize("/complicated/**", access("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
  650. // ...
  651. authorize(anyRequest, denyAll)
  652. }
  653. }
  654. ----
  655. ====
  656. Then you can compose your own `AuthorizationManager` with Spring Security authorization primitives like so:
  657. ====
  658. .Java
  659. [source,java,role="primary"]
  660. ----
  661. http
  662. .authorizeHttpRequests((authorize) -> authorize
  663. .shouldFilterAllDispatcherTypes(false)
  664. .mvcMatchers("/complicated/**").access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
  665. // ...
  666. .anyRequest().denyAll()
  667. )
  668. // ...
  669. ----
  670. .Kotlin
  671. [source,kotlin,role="secondary"]
  672. ----
  673. http {
  674. authorizeHttpRequests {
  675. shouldFilterAllDispatcherTypes = false
  676. authorize("/complicated/**", access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
  677. // ...
  678. authorize(anyRequest, denyAll)
  679. }
  680. }
  681. ----
  682. ====
  683. Or you can use `WebExpressionAuthorizationManager` in the following way:
  684. ====
  685. .Java
  686. [source,java,role="primary"]
  687. ----
  688. http
  689. .authorizeRequests((authorize) -> authorize
  690. .filterSecurityInterceptorOncePerRequest(true)
  691. .mvcMatchers("/complicated/**").access(
  692. new WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
  693. )
  694. // ...
  695. .anyRequest().denyAll()
  696. )
  697. // ...
  698. ----
  699. .Kotlin
  700. [source,kotlin,role="secondary"]
  701. ----
  702. http {
  703. authorizeRequests {
  704. filterSecurityInterceptorOncePerRequest = true
  705. authorize("/complicated/**", access(
  706. WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
  707. )
  708. // ...
  709. authorize(anyRequest, denyAll)
  710. }
  711. }
  712. ----
  713. ====
  714. ==== Switch to filter all dispatcher types
  715. Spring Security 5.8 and earlier only xref:servlet/authorization/architecture.adoc[perform authorization] once per request.
  716. This means that dispatcher types like `FORWARD` and `INCLUDE` that run after `REQUEST` are not secured by default.
  717. It's recommended that Spring Security secure all dispatch types.
  718. As such, in 6.0, Spring Security changes this default.
  719. So, finally, change your authorization rules to filter all dispatcher types.
  720. To do this, change:
  721. ====
  722. .Java
  723. [source,java,role="primary"]
  724. ----
  725. http
  726. .authorizeHttpRequests((authorize) -> authorize
  727. .shouldFilterAllDispatcherTypes(false)
  728. .mvcMatchers("/app/**").hasRole("APP")
  729. // ...
  730. .anyRequest().denyAll()
  731. )
  732. // ...
  733. ----
  734. .Kotlin
  735. [source,kotlin,role="secondary"]
  736. ----
  737. http {
  738. authorizeHttpRequests {
  739. shouldFilterAllDispatcherTypes = false
  740. authorize("/app/**", hasRole("APP"))
  741. // ...
  742. authorize(anyRequest, denyAll)
  743. }
  744. }
  745. ----
  746. .Xml
  747. [source,xml,role="secondary"]
  748. ----
  749. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  750. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  751. <!-- ... -->
  752. <intercept-url pattern="/**" access="denyAll"/>
  753. </http>
  754. ----
  755. ====
  756. to:
  757. ====
  758. .Java
  759. [source,java,role="primary"]
  760. ----
  761. http
  762. .authorizeHttpRequests((authorize) -> authorize
  763. .shouldFilterAllDispatcherTypes(true)
  764. .mvcMatchers("/app/**").hasRole("APP")
  765. // ...
  766. .anyRequest().denyAll()
  767. )
  768. // ...
  769. ----
  770. .Kotlin
  771. [source,kotlin,role="secondary"]
  772. ----
  773. http {
  774. authorizeHttpRequests {
  775. shouldFilterAllDispatcherTypes = true
  776. authorize("/app/**", hasRole("APP"))
  777. // ...
  778. authorize(anyRequest, denyAll)
  779. }
  780. }
  781. ----
  782. .Xml
  783. [source,xml,role="secondary"]
  784. ----
  785. <http filter-all-dispatcher-types="true" use-authorization-manager="true">
  786. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  787. <!-- ... -->
  788. <intercept-url pattern="/**" access="denyAll"/>
  789. </http>
  790. ----
  791. ====
  792. [[servlet-authorizationmanager-requests-opt-out]]
  793. ==== Opt-out Steps
  794. In case you had trouble, take a look at these scenarios for optimal opt out behavior:
  795. ===== I cannot secure all dispatcher types
  796. If you cannot secure all dispatcher types, first try and declare which dispatcher types should not require authorization like so:
  797. ====
  798. .Java
  799. [source,java,role="primary"]
  800. ----
  801. http
  802. .authorizeHttpRequests((authorize) -> authorize
  803. .shouldFilterAllDispatcherTypes(true)
  804. .dispatcherTypeMatchers(FORWARD, INCLUDE).permitAll()
  805. .mvcMatchers("/app/**").hasRole("APP")
  806. // ...
  807. .anyRequest().denyAll()
  808. )
  809. // ...
  810. ----
  811. .Kotlin
  812. [source,kotlin,role="secondary"]
  813. ----
  814. http {
  815. authorizeHttpRequests {
  816. shouldFilterAllDispatcherTypes = true
  817. authorize(DispatcherTypeRequestMatcher(FORWARD, INCLUDE), permitAll)
  818. authorize("/app/**", hasRole("APP"))
  819. // ...
  820. authorize(anyRequest, denyAll)
  821. }
  822. }
  823. ----
  824. .Xml
  825. [source,xml,role="secondary"]
  826. ----
  827. <http filter-all-dispatcher-types="true" use-authorization-manager="true">
  828. <intercept-url request-matcher-ref="dispatchers"/>
  829. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  830. <!-- ... -->
  831. <intercept-url pattern="/**" access="denyAll"/>
  832. </http>
  833. <bean id="dispatchers" class="org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher">
  834. <constructor-arg>
  835. <util:list value-type="javax.servlet.DispatcherType">
  836. <value>FORWARD</value>
  837. <value>INCLUDE</value>
  838. </util:list>
  839. </constructor-arg>
  840. </bean>
  841. ----
  842. ====
  843. Or, if that doesn't work, then you can explicitly opt out of the behavior by setting `filter-all-dispatcher-types` and `filterAllDispatcherTypes` to `false`:
  844. ====
  845. .Java
  846. [source,java,role="primary"]
  847. ----
  848. http
  849. .authorizeHttpRequests((authorize) -> authorize
  850. .filterAllDispatcherTypes(false)
  851. .mvcMatchers("/app/**").hasRole("APP")
  852. // ...
  853. )
  854. // ...
  855. ----
  856. .Kotlin
  857. [source,kotlin,role="secondary"]
  858. ----
  859. http {
  860. authorizeHttpRequests {
  861. filterAllDispatcherTypes = false
  862. authorize("/messages/**", hasRole("APP"))
  863. // ...
  864. }
  865. }
  866. ----
  867. .Xml
  868. [source,xml,role="secondary"]
  869. ----
  870. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  871. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  872. <!-- ... -->
  873. </http>
  874. ----
  875. ====
  876. or, if you are still using `authorizeRequests` or `use-authorization-manager="false"`, set `oncePerRequest` to `true`:
  877. ====
  878. .Java
  879. [source,java,role="primary"]
  880. ----
  881. http
  882. .authorizeRequests((authorize) -> authorize
  883. .filterSecurityInterceptorOncePerRequest(true)
  884. .mvcMatchers("/app/**").hasRole("APP")
  885. // ...
  886. )
  887. // ...
  888. ----
  889. .Kotlin
  890. [source,kotlin,role="secondary"]
  891. ----
  892. http {
  893. authorizeRequests {
  894. filterSecurityInterceptorOncePerRequest = true
  895. authorize("/messages/**", hasRole("APP"))
  896. // ...
  897. }
  898. }
  899. ----
  900. .Xml
  901. [source,xml,role="secondary"]
  902. ----
  903. <http once-per-request="true" use-authorization-manager="false">
  904. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  905. <!-- ... -->
  906. </http>
  907. ----
  908. ====
  909. ===== I cannot declare an authorization rule for all requests
  910. If you are having trouble setting an `anyRequest` authorization rule of `denyAll`, please use {security-api-url}org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.AuthorizedUrl.html#permitAll()[`permitAll`] instead, like so:
  911. ====
  912. .Java
  913. [source,java,role="primary"]
  914. ----
  915. http
  916. .authorizeHttpReqeusts((authorize) -> authorize
  917. .mvcMatchers("/app/*").hasRole("APP")
  918. // ...
  919. .anyRequest().permitAll()
  920. )
  921. ----
  922. .Kotlin
  923. [source,kotlin,role="secondary"]
  924. ----
  925. http {
  926. authorizeHttpRequests {
  927. authorize("/app*", hasRole("APP"))
  928. // ...
  929. authorize(anyRequest, permitAll)
  930. }
  931. }
  932. ----
  933. .Xml
  934. [source,xml,role="secondary"]
  935. ----
  936. <http>
  937. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  938. <!-- ... -->
  939. <intercept-url pattern="/**" access="permitAll"/>
  940. </http>
  941. ----
  942. ====
  943. ===== I cannot migrate my SpEL or my `AccessDecisionManager`
  944. If you are having trouble with SpEL, `AccessDecisionManager`, or there is some other feature that you are needing to keep using in `<http>` or `authorizeRequests`, try the following.
  945. First, if you still need `authorizeRequests`, you are welcome to keep using it. Even though it is deprecated, it is not removed in 6.0.
  946. Second, if you still need your custom `access-decision-manager-ref` or have some other reason to opt out of `AuthorizationManager`, do:
  947. ====
  948. .Xml
  949. [source,xml,role="secondary"]
  950. ----
  951. <http use-authorization-manager="false">
  952. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  953. <!-- ... -->
  954. </http>
  955. ----
  956. ====
  957. == Reactive
  958. === Use `AuthorizationManager` for Method Security
  959. 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.
  960. Should you run into trouble with making these changes, you can follow the
  961. <<reactive-authorizationmanager-methods-opt-out,opt out steps>> at the end of this section.
  962. '''
  963. 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.
  964. [[reactive-change-to-useauthorizationmanager]]
  965. ==== Change `useAuthorizationManager` to `true`
  966. To opt in, change `useAuthorizationManager` to `true` like so:
  967. ====
  968. .Java
  969. [source,java,role="primary"]
  970. ----
  971. @EnableReactiveMethodSecurity
  972. ----
  973. .Kotlin
  974. [source,kotlin,role="secondary"]
  975. ----
  976. @EnableReactiveMethodSecurity
  977. ----
  978. ====
  979. changes to:
  980. ====
  981. .Java
  982. [source,java,role="primary"]
  983. ----
  984. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  985. ----
  986. .Kotlin
  987. [source,kotlin,role="secondary"]
  988. ----
  989. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  990. ----
  991. ====
  992. '''
  993. [[reactive-check-for-annotationconfigurationexceptions]]
  994. ==== Check for ``AnnotationConfigurationException``s
  995. `useAuthorizationManager` activates stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
  996. 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.
  997. [[reactive-authorizationmanager-methods-opt-out]]
  998. ==== Opt-out Steps
  999. If you ran into trouble with `AuthorizationManager` for reactive method security, you can opt out by changing:
  1000. ====
  1001. .Java
  1002. [source,java,role="primary"]
  1003. ----
  1004. @EnableReactiveMethodSecurity
  1005. ----
  1006. .Kotlin
  1007. [source,kotlin,role="secondary"]
  1008. ----
  1009. @EnableReactiveMethodSecurity
  1010. ----
  1011. ====
  1012. to:
  1013. ====
  1014. .Java
  1015. [source,java,role="primary"]
  1016. ----
  1017. @EnableReactiveMethodSecurity(useAuthorizationManager = false)
  1018. ----
  1019. .Kotlin
  1020. [source,kotlin,role="secondary"]
  1021. ----
  1022. @EnableReactiveMethodSecurity(useAuthorizationManager = false)
  1023. ----
  1024. ====