migration.adoc 75 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246
  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. === Defer Loading CsrfToken
  14. In Spring Security 5, the default behavior is that the `CsrfToken` will be loaded on every request.
  15. This means that in a typical setup, the `HttpSession` must be read for every request even if it is unnecessary.
  16. In Spring Security 6, the default is that the lookup of the `CsrfToken` will be deferred until it is needed.
  17. To opt into the new Spring Security 6 default, the following configuration can be used.
  18. .Defer Loading `CsrfToken`
  19. ====
  20. .Java
  21. [source,java,role="primary"]
  22. ----
  23. @Bean
  24. DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
  25. CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
  26. // set the name of the attribute the CsrfToken will be populated on
  27. requestHandler.setCsrfRequestAttributeName("_csrf");
  28. http
  29. // ...
  30. .csrf((csrf) -> csrf
  31. .csrfTokenRequestHandler(requestHandler)
  32. );
  33. return http.build();
  34. }
  35. ----
  36. .Kotlin
  37. [source,kotlin,role="secondary"]
  38. ----
  39. @Bean
  40. open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
  41. val requestHandler = CsrfTokenRequestAttributeHandler()
  42. // set the name of the attribute the CsrfToken will be populated on
  43. requestHandler.setCsrfRequestAttributeName("_csrf")
  44. http {
  45. csrf {
  46. csrfTokenRequestHandler = requestHandler
  47. }
  48. }
  49. return http.build()
  50. }
  51. ----
  52. .XML
  53. [source,xml,role="secondary"]
  54. ----
  55. <http>
  56. <!-- ... -->
  57. <csrf request-handler-ref="requestHandler"/>
  58. </http>
  59. <b:bean id="requestHandler"
  60. class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"
  61. p:csrfRequestAttributeName="_csrf"/>
  62. ----
  63. ====
  64. If this breaks your application, then you can explicitly opt into the 5.8 defaults using the following configuration:
  65. .Explicit Configure `CsrfToken` with 5.8 Defaults
  66. ====
  67. .Java
  68. [source,java,role="primary"]
  69. ----
  70. @Bean
  71. DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
  72. CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
  73. // set the name of the attribute the CsrfToken will be populated on
  74. requestHandler.setCsrfRequestAttributeName(null);
  75. http
  76. // ...
  77. .csrf((csrf) -> csrf
  78. .csrfTokenRequestHandler(requestHandler)
  79. );
  80. return http.build();
  81. }
  82. ----
  83. .Kotlin
  84. [source,kotlin,role="secondary"]
  85. ----
  86. @Bean
  87. open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
  88. val requestHandler = CsrfTokenRequestAttributeHandler()
  89. // set the name of the attribute the CsrfToken will be populated on
  90. requestHandler.setCsrfRequestAttributeName(null)
  91. http {
  92. csrf {
  93. csrfTokenRequestHandler = requestHandler
  94. }
  95. }
  96. return http.build()
  97. }
  98. ----
  99. .XML
  100. [source,xml,role="secondary"]
  101. ----
  102. <http>
  103. <!-- ... -->
  104. <csrf request-handler-ref="requestHandler"/>
  105. </http>
  106. <b:bean id="requestHandler"
  107. class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler">
  108. <b:property name="csrfRequestAttributeName">
  109. <b:null/>
  110. </b:property>
  111. </b:bean>
  112. ----
  113. ====
  114. === CSRF BREACH Protection
  115. If the steps for <<Defer Loading CsrfToken>> work for you, then you can also opt into Spring Security 6's default support for BREACH protection of the `CsrfToken` using the following configuration:
  116. .`CsrfToken` BREACH Protection
  117. ====
  118. .Java
  119. [source,java,role="primary"]
  120. ----
  121. @Bean
  122. DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
  123. XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler();
  124. // set the name of the attribute the CsrfToken will be populated on
  125. requestHandler.setCsrfRequestAttributeName("_csrf");
  126. http
  127. // ...
  128. .csrf((csrf) -> csrf
  129. .csrfTokenRequestHandler(requestHandler)
  130. );
  131. return http.build();
  132. }
  133. ----
  134. .Kotlin
  135. [source,kotlin,role="secondary"]
  136. ----
  137. @Bean
  138. open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
  139. val requestHandler = XorCsrfTokenRequestAttributeHandler()
  140. // set the name of the attribute the CsrfToken will be populated on
  141. requestHandler.setCsrfRequestAttributeName("_csrf")
  142. http {
  143. csrf {
  144. csrfTokenRequestHandler = requestHandler
  145. }
  146. }
  147. return http.build()
  148. }
  149. ----
  150. .XML
  151. [source,xml,role="secondary"]
  152. ----
  153. <http>
  154. <!-- ... -->
  155. <csrf request-handler-ref="requestHandler"/>
  156. </http>
  157. <b:bean id="requestHandler"
  158. class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"
  159. p:csrfRequestAttributeName="_csrf"/>
  160. ----
  161. ====
  162. === Explicit Save SecurityContextRepository
  163. 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`].
  164. Saving must be done just prior to the `HttpServletResponse` being committed and just before `SecurityContextPersistenceFilter`.
  165. 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`).
  166. 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.
  167. 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`.
  168. Users now must explicitly save the `SecurityContext` with the `SecurityContextRepository` if they want the `SecurityContext` to persist between requests.
  169. This removes ambiguity and improves performance by only requiring writing to the `SecurityContextRepository` (i.e. `HttpSession`) when it is necessary.
  170. To opt into the new Spring Security 6 default, the following configuration can be used.
  171. include::partial$servlet/architecture/security-context-explicit.adoc[]
  172. [[requestcache-query-optimization]]
  173. === Optimize Querying of `RequestCache`
  174. In Spring Security 5, the default behavior is to query the xref:servlet/architecture.adoc#savedrequests[saved request] on every request.
  175. 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.
  176. In Spring Security 6, the default is that `RequestCache` will only be queried for a cached request if the HTTP parameter `continue` is defined.
  177. This allows Spring Security to avoid unnecessarily reading the `HttpSession` with the `RequestCache`.
  178. In Spring Security 5 the default is to use `HttpSessionRequestCache` which will be queried for a cached request on every request.
  179. 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:
  180. include::partial$servlet/architecture/request-cache-continue.adoc[]
  181. === Use `AuthorizationManager` for Method Security
  182. 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.
  183. 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.
  184. [[servlet-replace-globalmethodsecurity-with-methodsecurity]]
  185. ==== 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]
  186. {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.
  187. 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.
  188. This means that the following two listings are functionally equivalent:
  189. ====
  190. .Java
  191. [source,java,role="primary"]
  192. ----
  193. @EnableGlobalMethodSecurity(prePostEnabled = true)
  194. ----
  195. .Kotlin
  196. [source,kotlin,role="secondary"]
  197. ----
  198. @EnableGlobalMethodSecurity(prePostEnabled = true)
  199. ----
  200. .Xml
  201. [source,xml,role="secondary"]
  202. ----
  203. <global-method-security pre-post-enabled="true"/>
  204. ----
  205. ====
  206. and:
  207. ====
  208. .Java
  209. [source,java,role="primary"]
  210. ----
  211. @EnableMethodSecurity
  212. ----
  213. .Kotlin
  214. [source,kotlin,role="secondary"]
  215. ----
  216. @EnableMethodSecurity
  217. ----
  218. .Xml
  219. [source,xml,role="secondary"]
  220. ----
  221. <method-security/>
  222. ----
  223. ====
  224. For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior.
  225. For example, a listing like:
  226. ====
  227. .Java
  228. [source,java,role="primary"]
  229. ----
  230. @EnableGlobalMethodSecurity(securedEnabled = true)
  231. ----
  232. .Kotlin
  233. [source,kotlin,role="secondary"]
  234. ----
  235. @EnableGlobalMethodSecurity(securedEnabled = true)
  236. ----
  237. .Xml
  238. [source,xml,role="secondary"]
  239. ----
  240. <global-method-security secured-enabled="true"/>
  241. ----
  242. ====
  243. should change to:
  244. ====
  245. .Java
  246. [source,java,role="primary"]
  247. ----
  248. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  249. ----
  250. .Kotlin
  251. [source,kotlin,role="secondary"]
  252. ----
  253. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  254. ----
  255. .Xml
  256. [source,xml,role="secondary"]
  257. ----
  258. <method-security secured-enabled="true" pre-post-enabled="false"/>
  259. ----
  260. ====
  261. [[servlet-replace-permissionevaluator-bean-with-methodsecurityexpression-handler]]
  262. ==== Publish a `MethodSecurityExpressionHandler` instead of a `PermissionEvaluator`
  263. `@EnableMethodSecurity` does not pick up a `PermissionEvaluator`.
  264. This helps keep its API simple.
  265. If you have a custom {security-api-url}org/springframework/security/access/PermissionEvaluator.html[`PermissionEvaluator`] `@Bean`, please change it from:
  266. ====
  267. .Java
  268. [source,java,role="primary"]
  269. ----
  270. @Bean
  271. static PermissionEvaluator permissionEvaluator() {
  272. // ... your evaluator
  273. }
  274. ----
  275. .Kotlin
  276. [source,kotlin,role="secondary"]
  277. ----
  278. companion object {
  279. @Bean
  280. fun permissionEvaluator(): PermissionEvaluator {
  281. // ... your evaluator
  282. }
  283. }
  284. ----
  285. ====
  286. to:
  287. ====
  288. .Java
  289. [source,java,role="primary"]
  290. ----
  291. @Bean
  292. static MethodSecurityExpressionHandler expressionHandler() {
  293. var expressionHandler = new DefaultMethodSecurityExpressionHandler();
  294. expressionHandler.setPermissionEvaluator(myPermissionEvaluator);
  295. return expressionHandler;
  296. }
  297. ----
  298. .Kotlin
  299. [source,kotlin,role="secondary"]
  300. ----
  301. companion object {
  302. @Bean
  303. fun expressionHandler(): MethodSecurityExpressionHandler {
  304. val expressionHandler = DefaultMethodSecurityExpressionHandler
  305. expressionHandler.setPermissionEvaluator(myPermissionEvaluator)
  306. return expressionHandler
  307. }
  308. }
  309. ----
  310. ====
  311. ==== Replace any custom method-security ``AccessDecisionManager``s
  312. Your application may have a custom {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] or {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] arrangement.
  313. The preparation strategy will depend on your reason for each arrangement.
  314. Read on to find the best match for your situation.
  315. ===== I use `UnanimousBased`
  316. If your application uses {security-api-url}org/springframework/security/access/vote/UnanimousBased.html[`UnanimousBased`] with the default voters, you likely need do nothing since unanimous-based is the default behavior with {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.html[`@EnableMethodSecurity`].
  317. However, if you do discover that you cannot accept the default authorization managers, you can use `AuthorizationManagers.allOf` to compose your own arrangement.
  318. Having done that, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`].
  319. ===== I use `AffirmativeBased`
  320. If your application uses {security-api-url}org/springframework/security/access/vote/AffirmativeBased.html[`AffirmativeBased`], then you can construct an equivalent {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`], like so:
  321. ====
  322. .Java
  323. [source,java,role="primary"]
  324. ----
  325. AuthorizationManager<MethodInvocation> authorization = AuthorizationManagers.anyOf(
  326. // ... your list of authorization managers
  327. )
  328. ----
  329. .Kotlin
  330. [source,kotlin,role="secondary"]
  331. ----
  332. val authorization = AuthorizationManagers.anyOf(
  333. // ... your list of authorization managers
  334. )
  335. ----
  336. ====
  337. Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`].
  338. ===== I use `ConsensusBased`
  339. There is no framework-provided equivalent for {security-api-url}org/springframework/security/access/vote/ConsensusBased.html[`ConsensusBased`].
  340. In that case, please implement a composite {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] that takes the set of delegate ``AuthorizationManager``s into account.
  341. Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`].
  342. ===== I use a custom `AccessDecisionVoter`
  343. You should either change the class to implement {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] or create an adapter.
  344. Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution.
  345. By way of example, though, here is what adapting {security-api-url}org/springframework/security/access/SecurityMetadataSource.html[`SecurityMetadataSource`] and {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] for `@PreAuthorize` would look like:
  346. ====
  347. .Java
  348. [source,java,role="primary"]
  349. ----
  350. public final class PreAuthorizeAuthorizationManagerAdapter implements AuthorizationManager<MethodInvocation> {
  351. private final SecurityMetadataSource metadata;
  352. private final AccessDecisionVoter voter;
  353. public PreAuthorizeAuthorizationManagerAdapter(MethodSecurityExpressionHandler expressionHandler) {
  354. ExpressionBasedAnnotationAttributeFactory attributeFactory =
  355. new ExpressionBasedAnnotationAttributeFactory(expressionHandler);
  356. this.metadata = new PrePostAnnotationSecurityMetadataSource(attributeFactory);
  357. ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
  358. expressionAdvice.setExpressionHandler(expressionHandler);
  359. this.voter = new PreInvocationAuthorizationAdviceVoter(expressionAdvice);
  360. }
  361. public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
  362. List<ConfigAttribute> attributes = this.metadata.getAttributes(invocation, AopUtils.getTargetClass(invocation.getThis()));
  363. int decision = this.voter.vote(authentication.get(), invocation, attributes);
  364. if (decision == ACCESS_GRANTED) {
  365. return new AuthorizationDecision(true);
  366. }
  367. if (decision == ACCESS_DENIED) {
  368. return new AuthorizationDecision(false);
  369. }
  370. return null; // abstain
  371. }
  372. }
  373. ----
  374. ====
  375. Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`].
  376. ===== I use a custom `AfterInvocationManager`
  377. {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] replaces both {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] and {security-api-url}org/springframework/security/access/intercept/AfterInvocationManager.html[`AfterInvocationManager`].
  378. The difference is that `AuthorizationManager<MethodInvocation>` replaces `AccessDecisionManager` and `AuthorizationManager<MethodInvocationResult>` replaces `AfterInvocationManager`.
  379. Given that, <<_i_use_a_custom_accessdecisionvoter,the same rules apply for adaptation>>, where the goal this time is to implement `AuthorizationManager<MethodInvocationResult>` instead of `AuthorizationManager<MethodInvocation>` and use `AuthorizationManagerAfterMethodInterceptor` instead of `AuthorizationManagerBeforeMethodInterceptor`.
  380. ===== I use `RunAsManager`
  381. There is currently https://github.com/spring-projects/spring-security/issues/11331[no replacement for `RunAsManager`] though one is being considered.
  382. It is quite straightforward to adapt a `RunAsManager`, though, to the `AuthorizationManager` API, if needed.
  383. Here is some pseudocode to get you started:
  384. ====
  385. .Java
  386. [source,java,role="primary"]
  387. ----
  388. public final class RunAsAuthorizationManagerAdapter<T> implements AuthorizationManager<T> {
  389. private final RunAsManager runAs = new RunAsManagerImpl();
  390. private final SecurityMetadataSource metadata;
  391. private final AuthorizationManager<T> authorization;
  392. // ... constructor
  393. public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
  394. Supplier<Authentication> wrapped = (auth) -> {
  395. List<ConfigAttribute> attributes = this.metadata.getAttributes(object);
  396. return this.runAs.buildRunAs(auth, object, attributes);
  397. };
  398. return this.authorization.check(wrapped, object);
  399. }
  400. }
  401. ----
  402. ====
  403. Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`].
  404. [[servlet-check-for-annotationconfigurationexceptions]]
  405. ==== Check for ``AnnotationConfigurationException``s
  406. `@EnableMethodSecurity` and `<method-security>` activate stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
  407. 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.
  408. === Use `AuthorizationManager` for Message Security
  409. 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.
  410. 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.
  411. ==== Ensure all messages have defined authorization rules
  412. The now-deprecated {security-api-url}org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.html[message security support] permits all messages by default.
  413. xref:servlet/integrations/websocket.adoc[The new support] has the stronger default of denying all messages.
  414. To prepare for this, ensure that authorization rules exist are declared for every request.
  415. For example, an application configuration like:
  416. ====
  417. .Java
  418. [source,java,role="primary"]
  419. ----
  420. @Override
  421. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  422. messages
  423. .simpDestMatchers("/user/queue/errors").permitAll()
  424. .simpDestMatchers("/admin/**").hasRole("ADMIN");
  425. }
  426. ----
  427. .Kotlin
  428. [source,kotlin,role="secondary"]
  429. ----
  430. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  431. messages
  432. .simpDestMatchers("/user/queue/errors").permitAll()
  433. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  434. }
  435. ----
  436. .Xml
  437. [source,xml,role="secondary"]
  438. ----
  439. <websocket-message-broker>
  440. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  441. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  442. </websocket-message-broker>
  443. ----
  444. ====
  445. should change to:
  446. ====
  447. .Java
  448. [source,java,role="primary"]
  449. ----
  450. @Override
  451. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  452. messages
  453. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  454. .simpDestMatchers("/user/queue/errors").permitAll()
  455. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  456. .anyMessage().denyAll();
  457. }
  458. ----
  459. .Kotlin
  460. [source,kotlin,role="secondary"]
  461. ----
  462. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  463. messages
  464. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  465. .simpDestMatchers("/user/queue/errors").permitAll()
  466. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  467. .anyMessage().denyAll()
  468. }
  469. ----
  470. .Xml
  471. [source,xml,role="secondary"]
  472. ----
  473. <websocket-message-broker>
  474. <intercept-message type="CONNECT" access="permitAll"/>
  475. <intercept-message type="DISCONNECT" access="permitAll"/>
  476. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  477. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  478. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  479. <intercept-message pattern="/**" access="denyAll"/>
  480. </websocket-message-broker>
  481. ----
  482. ====
  483. ==== Add `@EnableWebSocketSecurity`
  484. [NOTE]
  485. ====
  486. If you want to have CSRF disabled and you are using Java configuration, the migration steps are slightly different.
  487. Instead of using `@EnableWebSocketSecurity`, you will override the appropriate methods in `WebSocketMessageBrokerConfigurer` yourself.
  488. Please see xref:servlet/integrations/websocket.adoc#websocket-sameorigin-disable[the reference manual] for details about this step.
  489. ====
  490. If you are using Java Configuration, add {security-api-url}org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.html[`@EnableWebSocketSecurity`] to your application.
  491. For example, you can add it to your websocket security configuration class, like so:
  492. ====
  493. .Java
  494. [source,java,role="primary"]
  495. ----
  496. @EnableWebSocketSecurity
  497. @Configuration
  498. public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
  499. // ...
  500. }
  501. ----
  502. .Kotlin
  503. [source,kotlin,role="secondary"]
  504. ----
  505. @EnableWebSocketSecurity
  506. @Configuration
  507. class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
  508. // ...
  509. }
  510. ----
  511. ====
  512. This will make a prototype instance of `MessageMatcherDelegatingAuthorizationManager.Builder` available to encourage configuration by composition instead of extension.
  513. ==== Use an `AuthorizationManager<Message<?>>` instance
  514. To start using `AuthorizationManager`, you can set the `use-authorization-manager` attribute in XML or you can publish an `AuthorizationManager<Message<?>>` `@Bean` in Java.
  515. For example, the following application configuration:
  516. ====
  517. .Java
  518. [source,java,role="primary"]
  519. ----
  520. @Override
  521. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  522. messages
  523. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  524. .simpDestMatchers("/user/queue/errors").permitAll()
  525. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  526. .anyMessage().denyAll();
  527. }
  528. ----
  529. .Kotlin
  530. [source,kotlin,role="secondary"]
  531. ----
  532. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  533. messages
  534. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  535. .simpDestMatchers("/user/queue/errors").permitAll()
  536. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  537. .anyMessage().denyAll()
  538. }
  539. ----
  540. .Xml
  541. [source,xml,role="secondary"]
  542. ----
  543. <websocket-message-broker>
  544. <intercept-message type="CONNECT" access="permitAll"/>
  545. <intercept-message type="DISCONNECT" access="permitAll"/>
  546. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  547. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  548. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  549. <intercept-message pattern="/**" access="denyAll"/>
  550. </websocket-message-broker>
  551. ----
  552. ====
  553. changes to:
  554. ====
  555. .Java
  556. [source,java,role="primary"]
  557. ----
  558. @Bean
  559. AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
  560. messages
  561. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  562. .simpDestMatchers("/user/queue/errors").permitAll()
  563. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  564. .anyMessage().denyAll();
  565. return messages.build();
  566. }
  567. ----
  568. .Kotlin
  569. [source,kotlin,role="secondary"]
  570. ----
  571. @Bean
  572. fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
  573. messages
  574. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  575. .simpDestMatchers("/user/queue/errors").permitAll()
  576. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  577. .anyMessage().denyAll()
  578. return messages.build()
  579. }
  580. ----
  581. .Xml
  582. [source,xml,role="secondary"]
  583. ----
  584. <websocket-message-broker use-authorization-manager="true">
  585. <intercept-message type="CONNECT" access="permitAll"/>
  586. <intercept-message type="DISCONNECT" access="permitAll"/>
  587. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  588. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  589. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  590. <intercept-message pattern="/**" access="denyAll"/>
  591. </websocket-message-broker>
  592. ----
  593. ====
  594. ==== Stop Implementing `AbstractSecurityWebSocketMessageBrokerConfigurer`
  595. If you are using Java configuration, you can now simply extend `WebSocketMessageBrokerConfigurer`.
  596. For example, if your class that extends `AbstractSecurityWebSocketMessageBrokerConfigurer` is called `WebSocketSecurityConfig`, then:
  597. ====
  598. .Java
  599. [source,java,role="primary"]
  600. ----
  601. @EnableWebSocketSecurity
  602. @Configuration
  603. public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
  604. // ...
  605. }
  606. ----
  607. .Kotlin
  608. [source,kotlin,role="secondary"]
  609. ----
  610. @EnableWebSocketSecurity
  611. @Configuration
  612. class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
  613. // ...
  614. }
  615. ----
  616. ====
  617. changes to:
  618. ====
  619. .Java
  620. [source,java,role="primary"]
  621. ----
  622. @EnableWebSocketSecurity
  623. @Configuration
  624. public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
  625. // ...
  626. }
  627. ----
  628. .Kotlin
  629. [source,kotlin,role="secondary"]
  630. ----
  631. @EnableWebSocketSecurity
  632. @Configuration
  633. class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer {
  634. // ...
  635. }
  636. ----
  637. ====
  638. [[servlet-authorizationmanager-messages-opt-out]]
  639. ==== Opt-out Steps
  640. In case you had trouble, take a look at these scenarios for optimal opt out behavior:
  641. ===== I cannot declare an authorization rule for all requests
  642. 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:
  643. ====
  644. .Java
  645. [source,java,role="primary"]
  646. ----
  647. @Bean
  648. AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
  649. messages
  650. .simpDestMatchers("/user/queue/errors").permitAll()
  651. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  652. // ...
  653. .anyMessage().permitAll();
  654. return messages.build();
  655. }
  656. ----
  657. .Kotlin
  658. [source,kotlin,role="secondary"]
  659. ----
  660. @Bean
  661. fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
  662. messages
  663. .simpDestMatchers("/user/queue/errors").permitAll()
  664. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  665. // ...
  666. .anyMessage().permitAll();
  667. return messages.build()
  668. }
  669. ----
  670. .Xml
  671. [source,xml,role="secondary"]
  672. ----
  673. <websocket-message-broker use-authorization-manager="true">
  674. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  675. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  676. <!-- ... -->
  677. <intercept-message pattern="/**" access="permitAll"/>
  678. </websocket-message-broker>
  679. ----
  680. ====
  681. ===== I cannot get CSRF working, need some other `AbstractSecurityWebSocketMessageBrokerConfigurer` feature, or am having trouble with `AuthorizationManager`
  682. In the case of Java, you may continue using `AbstractMessageSecurityWebSocketMessageBrokerConfigurer`.
  683. Even though it is deprecated, it will not be removed in 6.0.
  684. In the case of XML, you can opt out of `AuthorizationManager` by setting `use-authorization-manager="false"`:
  685. ====
  686. .Xml
  687. [source,xml,role="secondary"]
  688. ----
  689. <websocket-message-broker>
  690. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  691. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  692. </websocket-message-broker>
  693. ----
  694. ====
  695. to:
  696. ====
  697. .Xml
  698. [source,xml,role="secondary"]
  699. ----
  700. <websocket-message-broker use-authorization-manager="false">
  701. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  702. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  703. </websocket-message-broker>
  704. ----
  705. ====
  706. === Use `AuthorizationManager` for Request Security
  707. 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].
  708. 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.
  709. ==== Ensure that all requests have defined authorization rules
  710. In Spring Security 5.8 and earlier, requests with no authorization rule are permitted by default.
  711. It is a stronger security position to deny by default, thus requiring that authorization rules be clearly defined for every endpoint.
  712. As such, in 6.0, Spring Security by default denies any request that is missing an authorization rule.
  713. 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.
  714. 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.
  715. [NOTE]
  716. ====
  717. You may already have an `anyRequest` rule defined that you are happy with in which case this step can be skipped.
  718. ====
  719. Adding `denyAll` to the end looks like changing:
  720. ====
  721. .Java
  722. [source,java,role="primary"]
  723. ----
  724. http
  725. .authorizeRequests((authorize) -> authorize
  726. .filterSecurityInterceptorOncePerRequest(true)
  727. .mvcMatchers("/app/**").hasRole("APP")
  728. // ...
  729. )
  730. // ...
  731. ----
  732. .Kotlin
  733. [source,kotlin,role="secondary"]
  734. ----
  735. http {
  736. authorizeRequests {
  737. filterSecurityInterceptorOncePerRequest = true
  738. authorize("/app/**", hasRole("APP"))
  739. // ...
  740. }
  741. }
  742. ----
  743. .Xml
  744. [source,xml,role="secondary"]
  745. ----
  746. <http once-per-request="true">
  747. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  748. <!-- ... -->
  749. </http>
  750. ----
  751. ====
  752. to:
  753. ====
  754. .Java
  755. [source,java,role="primary"]
  756. ----
  757. http
  758. .authorizeRequests((authorize) -> authorize
  759. .filterSecurityInterceptorOncePerRequest(true)
  760. .mvcMatchers("/app/**").hasRole("APP")
  761. // ...
  762. .anyRequest().denyAll()
  763. )
  764. // ...
  765. ----
  766. .Kotlin
  767. [source,kotlin,role="secondary"]
  768. ----
  769. http {
  770. authorizeRequests {
  771. filterSecurityInterceptorOncePerRequest = true
  772. authorize("/app/**", hasRole("APP"))
  773. // ...
  774. authorize(anyRequest, denyAll)
  775. }
  776. }
  777. ----
  778. .Xml
  779. [source,xml,role="secondary"]
  780. ----
  781. <http once-per-request="true">
  782. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  783. <!-- ... -->
  784. <intercept-url pattern="/**" access="denyAll"/>
  785. </http>
  786. ----
  787. ====
  788. If you have already migrated to `authorizeHttpRequests`, the recommended change is the same.
  789. ==== Switch to `AuthorizationManager`
  790. 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.
  791. Change:
  792. ====
  793. .Java
  794. [source,java,role="primary"]
  795. ----
  796. http
  797. .authorizeRequests((authorize) -> authorize
  798. .filterSecurityInterceptorOncePerRequest(true)
  799. .mvcMatchers("/app/**").hasRole("APP")
  800. // ...
  801. .anyRequest().denyAll()
  802. )
  803. // ...
  804. ----
  805. .Kotlin
  806. [source,kotlin,role="secondary"]
  807. ----
  808. http {
  809. authorizeRequests {
  810. filterSecurityInterceptorOncePerRequest = true
  811. authorize("/app/**", hasRole("APP"))
  812. // ...
  813. authorize(anyRequest, denyAll)
  814. }
  815. }
  816. ----
  817. .Xml
  818. [source,xml,role="secondary"]
  819. ----
  820. <http once-per-request="true">
  821. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  822. <!-- ... -->
  823. <intercept-url pattern="/**" access="denyAll"/>
  824. </http>
  825. ----
  826. ====
  827. to:
  828. ====
  829. .Java
  830. [source,java,role="primary"]
  831. ----
  832. http
  833. .authorizeHttpRequests((authorize) -> authorize
  834. .shouldFilterAllDispatcherTypes(false)
  835. .mvcMatchers("/app/**").hasRole("APP")
  836. // ...
  837. .anyRequest().denyAll()
  838. )
  839. // ...
  840. ----
  841. .Kotlin
  842. [source,kotlin,role="secondary"]
  843. ----
  844. http {
  845. authorizeHttpRequests {
  846. shouldFilterAllDispatcherTypes = false
  847. authorize("/app/**", hasRole("APP"))
  848. // ...
  849. authorize(anyRequest, denyAll)
  850. }
  851. }
  852. ----
  853. .Xml
  854. [source,xml,role="secondary"]
  855. ----
  856. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  857. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  858. <!-- ... -->
  859. <intercept-url pattern="/**" access="denyAll"/>
  860. </http>
  861. ----
  862. ====
  863. ==== Migrate SpEL expressions to `AuthorizationManager`
  864. For authorization rules, Java tends to be easier to test and maintain than SpEL.
  865. As such, `authorizeHttpRequests` does not have a method for declaring a `String` SpEL.
  866. Instead, you can implement your own `AuthorizationManager` implementation or use `WebExpressionAuthorizationManager`.
  867. For completeness, both options will be demonstrated.
  868. First, if you have the following SpEL:
  869. ====
  870. .Java
  871. [source,java,role="primary"]
  872. ----
  873. http
  874. .authorizeRequests((authorize) -> authorize
  875. .filterSecurityInterceptorOncePerRequest(true)
  876. .mvcMatchers("/complicated/**").access("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
  877. // ...
  878. .anyRequest().denyAll()
  879. )
  880. // ...
  881. ----
  882. .Kotlin
  883. [source,kotlin,role="secondary"]
  884. ----
  885. http {
  886. authorizeRequests {
  887. filterSecurityInterceptorOncePerRequest = true
  888. authorize("/complicated/**", access("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
  889. // ...
  890. authorize(anyRequest, denyAll)
  891. }
  892. }
  893. ----
  894. ====
  895. Then you can compose your own `AuthorizationManager` with Spring Security authorization primitives like so:
  896. ====
  897. .Java
  898. [source,java,role="primary"]
  899. ----
  900. http
  901. .authorizeHttpRequests((authorize) -> authorize
  902. .shouldFilterAllDispatcherTypes(false)
  903. .mvcMatchers("/complicated/**").access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
  904. // ...
  905. .anyRequest().denyAll()
  906. )
  907. // ...
  908. ----
  909. .Kotlin
  910. [source,kotlin,role="secondary"]
  911. ----
  912. http {
  913. authorizeHttpRequests {
  914. shouldFilterAllDispatcherTypes = false
  915. authorize("/complicated/**", access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
  916. // ...
  917. authorize(anyRequest, denyAll)
  918. }
  919. }
  920. ----
  921. ====
  922. Or you can use `WebExpressionAuthorizationManager` in the following way:
  923. ====
  924. .Java
  925. [source,java,role="primary"]
  926. ----
  927. http
  928. .authorizeRequests((authorize) -> authorize
  929. .filterSecurityInterceptorOncePerRequest(true)
  930. .mvcMatchers("/complicated/**").access(
  931. new WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
  932. )
  933. // ...
  934. .anyRequest().denyAll()
  935. )
  936. // ...
  937. ----
  938. .Kotlin
  939. [source,kotlin,role="secondary"]
  940. ----
  941. http {
  942. authorizeRequests {
  943. filterSecurityInterceptorOncePerRequest = true
  944. authorize("/complicated/**", access(
  945. WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
  946. )
  947. // ...
  948. authorize(anyRequest, denyAll)
  949. }
  950. }
  951. ----
  952. ====
  953. ==== Switch to filter all dispatcher types
  954. Spring Security 5.8 and earlier only xref:servlet/authorization/architecture.adoc[perform authorization] once per request.
  955. This means that dispatcher types like `FORWARD` and `INCLUDE` that run after `REQUEST` are not secured by default.
  956. It's recommended that Spring Security secure all dispatch types.
  957. As such, in 6.0, Spring Security changes this default.
  958. So, finally, change your authorization rules to filter all dispatcher types.
  959. To do this, change:
  960. ====
  961. .Java
  962. [source,java,role="primary"]
  963. ----
  964. http
  965. .authorizeHttpRequests((authorize) -> authorize
  966. .shouldFilterAllDispatcherTypes(false)
  967. .mvcMatchers("/app/**").hasRole("APP")
  968. // ...
  969. .anyRequest().denyAll()
  970. )
  971. // ...
  972. ----
  973. .Kotlin
  974. [source,kotlin,role="secondary"]
  975. ----
  976. http {
  977. authorizeHttpRequests {
  978. shouldFilterAllDispatcherTypes = false
  979. authorize("/app/**", hasRole("APP"))
  980. // ...
  981. authorize(anyRequest, denyAll)
  982. }
  983. }
  984. ----
  985. .Xml
  986. [source,xml,role="secondary"]
  987. ----
  988. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  989. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  990. <!-- ... -->
  991. <intercept-url pattern="/**" access="denyAll"/>
  992. </http>
  993. ----
  994. ====
  995. to:
  996. ====
  997. .Java
  998. [source,java,role="primary"]
  999. ----
  1000. http
  1001. .authorizeHttpRequests((authorize) -> authorize
  1002. .shouldFilterAllDispatcherTypes(true)
  1003. .mvcMatchers("/app/**").hasRole("APP")
  1004. // ...
  1005. .anyRequest().denyAll()
  1006. )
  1007. // ...
  1008. ----
  1009. .Kotlin
  1010. [source,kotlin,role="secondary"]
  1011. ----
  1012. http {
  1013. authorizeHttpRequests {
  1014. shouldFilterAllDispatcherTypes = true
  1015. authorize("/app/**", hasRole("APP"))
  1016. // ...
  1017. authorize(anyRequest, denyAll)
  1018. }
  1019. }
  1020. ----
  1021. .Xml
  1022. [source,xml,role="secondary"]
  1023. ----
  1024. <http filter-all-dispatcher-types="true" use-authorization-manager="true">
  1025. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1026. <!-- ... -->
  1027. <intercept-url pattern="/**" access="denyAll"/>
  1028. </http>
  1029. ----
  1030. ====
  1031. ==== Replace any custom filter-security ``AccessDecisionManager``s
  1032. Your application may have a custom {security-api-url}org/springframework/security/access/AccessDecisionManager.html[`AccessDecisionManager`] or {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] arrangement.
  1033. The preparation strategy will depend on your reason for each arrangement.
  1034. Read on to find the best match for your situation.
  1035. ===== I use `UnanimousBased`
  1036. If your application uses {security-api-url}org/springframework/security/access/vote/UnanimousBased.html[`UnanimousBased`], you should first adapt or replace any ``AccessDecisionVoter``s and then you can construct an `AuthorizationManager` like so:
  1037. ====
  1038. .Java
  1039. [source,java,role="primary"]
  1040. ----
  1041. @Bean
  1042. AuthorizationManager<RequestAuthorizationContext> requestAuthorization() {
  1043. PolicyAuthorizationManager policy = ...;
  1044. LocalAuthorizationManager local = ...;
  1045. return AuthorizationMangers.allOf(policy, local);
  1046. }
  1047. ----
  1048. .Kotlin
  1049. [source,kotlin,role="secondary"]
  1050. ----
  1051. @Bean
  1052. fun requestAuthorization(): AuthorizationManager<RequestAuthorizationContext> {
  1053. val policy: PolicyAuthorizationManager = ...
  1054. val local: LocalAuthorizationManager = ...
  1055. return AuthorizationMangers.allOf(policy, local)
  1056. }
  1057. ----
  1058. .Xml
  1059. [source,xml,role="secondary"]
  1060. ----
  1061. <bean id="requestAuthorization" class="org.springframework.security.authorization.AuthorizationManagers"
  1062. factory-method="allOf">
  1063. <constructor-arg>
  1064. <util:list>
  1065. <bean class="my.PolicyAuthorizationManager"/>
  1066. <bean class="my.LocalAuthorizationManager"/>
  1067. </util:list>
  1068. </constructor-arg>
  1069. </bean>
  1070. ----
  1071. ====
  1072. then, wire it into the DSL like so:
  1073. ====
  1074. .Java
  1075. [source,java,role="primary"]
  1076. ----
  1077. http
  1078. .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
  1079. // ...
  1080. ----
  1081. .Kotlin
  1082. [source,kotlin,role="secondary"]
  1083. ----
  1084. http {
  1085. authorizeHttpRequests {
  1086. authorize(anyRequest, requestAuthorization)
  1087. }
  1088. // ...
  1089. }
  1090. ----
  1091. .Xml
  1092. [source,xml,role="secondary"]
  1093. ----
  1094. <http authorization-manager-ref="requestAuthorization"/>
  1095. ----
  1096. ====
  1097. [NOTE]
  1098. ====
  1099. `authorizeHttpRequests` is designed so that you can apply a custom `AuthorizationManager` to any url pattern.
  1100. See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[the reference] for more details.
  1101. ====
  1102. ===== I use `AffirmativeBased`
  1103. If your application uses {security-api-url}org/springframework/security/access/vote/AffirmativeBased.html[`AffirmativeBased`], then you can construct an equivalent {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`], like so:
  1104. ====
  1105. .Java
  1106. [source,java,role="primary"]
  1107. ----
  1108. @Bean
  1109. AuthorizationManager<RequestAuthorizationContext> requestAuthorization() {
  1110. PolicyAuthorizationManager policy = ...;
  1111. LocalAuthorizationManager local = ...;
  1112. return AuthorizationMangers.anyOf(policy, local);
  1113. }
  1114. ----
  1115. .Kotlin
  1116. [source,kotlin,role="secondary"]
  1117. ----
  1118. @Bean
  1119. fun requestAuthorization(): AuthorizationManager<RequestAuthorizationContext> {
  1120. val policy: PolicyAuthorizationManager = ...
  1121. val local: LocalAuthorizationManager = ...
  1122. return AuthorizationMangers.anyOf(policy, local)
  1123. }
  1124. ----
  1125. .Xml
  1126. [source,xml,role="secondary"]
  1127. ----
  1128. <bean id="requestAuthorization" class="org.springframework.security.authorization.AuthorizationManagers"
  1129. factory-method="anyOf">
  1130. <constructor-arg>
  1131. <util:list>
  1132. <bean class="my.PolicyAuthorizationManager"/>
  1133. <bean class="my.LocalAuthorizationManager"/>
  1134. </util:list>
  1135. </constructor-arg>
  1136. </bean>
  1137. ----
  1138. ====
  1139. then, wire it into the DSL like so:
  1140. ====
  1141. .Java
  1142. [source,java,role="primary"]
  1143. ----
  1144. http
  1145. .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
  1146. // ...
  1147. ----
  1148. .Kotlin
  1149. [source,kotlin,role="secondary"]
  1150. ----
  1151. http {
  1152. authorizeHttpRequests {
  1153. authorize(anyRequest, requestAuthorization)
  1154. }
  1155. // ...
  1156. }
  1157. ----
  1158. .Xml
  1159. [source,xml,role="secondary"]
  1160. ----
  1161. <http authorization-manager-ref="requestAuthorization"/>
  1162. ----
  1163. ====
  1164. [NOTE]
  1165. ====
  1166. `authorizeHttpRequests` is designed so that you can apply a custom `AuthorizationManager` to any url pattern.
  1167. See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[the reference] for more details.
  1168. ====
  1169. ===== I use `ConsensusBased`
  1170. There is no framework-provided equivalent for {security-api-url}org/springframework/security/access/vote/ConsensusBased.html[`ConsensusBased`].
  1171. In that case, please implement a composite {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] that takes the set of delegate ``AuthorizationManager``s into account.
  1172. Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[adding a custom `AuthorizationManager`].
  1173. ===== I use a custom `AccessDecisionVoter`
  1174. You should either change the class to implement {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] or create an adapter.
  1175. Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution.
  1176. By way of example, though, here is what adapting {security-api-url}org/springframework/security/access/SecurityMetadataSource.html[`SecurityMetadataSource`] and {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] for `anyRequest().authenticated()` would look like:
  1177. ====
  1178. .Java
  1179. [source,java,role="primary"]
  1180. ----
  1181. public final class AnyRequestAuthenticatedAuthorizationManagerAdapter implements AuthorizationManager<RequestAuthorizationContext> {
  1182. private final SecurityMetadataSource metadata;
  1183. private final AccessDecisionVoter voter;
  1184. public PreAuthorizeAuthorizationManagerAdapter(SecurityExpressionHandler expressionHandler) {
  1185. Map<RequestMatcher, List<ConfigAttribute>> requestMap = Collections.singletonMap(
  1186. AnyRequestMatcher.INSTANCE, Collections.singletonList(new SecurityConfig("authenticated")));
  1187. this.metadata = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
  1188. WebExpressionVoter voter = new WebExpressionVoter();
  1189. voter.setExpressionHandler(expressionHandler);
  1190. this.voter = voter;
  1191. }
  1192. public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
  1193. List<ConfigAttribute> attributes = this.metadata.getAttributes(context);
  1194. int decision = this.voter.vote(authentication.get(), invocation, attributes);
  1195. if (decision == ACCESS_GRANTED) {
  1196. return new AuthorizationDecision(true);
  1197. }
  1198. if (decision == ACCESS_DENIED) {
  1199. return new AuthorizationDecision(false);
  1200. }
  1201. return null; // abstain
  1202. }
  1203. }
  1204. ----
  1205. ====
  1206. Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[adding a custom `AuthorizationManager`].
  1207. [[servlet-authorizationmanager-requests-opt-out]]
  1208. ==== Opt-out Steps
  1209. In case you had trouble, take a look at these scenarios for optimal opt out behavior:
  1210. ===== I cannot secure all dispatcher types
  1211. If you cannot secure all dispatcher types, first try and declare which dispatcher types should not require authorization like so:
  1212. ====
  1213. .Java
  1214. [source,java,role="primary"]
  1215. ----
  1216. http
  1217. .authorizeHttpRequests((authorize) -> authorize
  1218. .shouldFilterAllDispatcherTypes(true)
  1219. .dispatcherTypeMatchers(FORWARD, INCLUDE).permitAll()
  1220. .mvcMatchers("/app/**").hasRole("APP")
  1221. // ...
  1222. .anyRequest().denyAll()
  1223. )
  1224. // ...
  1225. ----
  1226. .Kotlin
  1227. [source,kotlin,role="secondary"]
  1228. ----
  1229. http {
  1230. authorizeHttpRequests {
  1231. shouldFilterAllDispatcherTypes = true
  1232. authorize(DispatcherTypeRequestMatcher(FORWARD, INCLUDE), permitAll)
  1233. authorize("/app/**", hasRole("APP"))
  1234. // ...
  1235. authorize(anyRequest, denyAll)
  1236. }
  1237. }
  1238. ----
  1239. .Xml
  1240. [source,xml,role="secondary"]
  1241. ----
  1242. <http filter-all-dispatcher-types="true" use-authorization-manager="true">
  1243. <intercept-url request-matcher-ref="dispatchers"/>
  1244. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1245. <!-- ... -->
  1246. <intercept-url pattern="/**" access="denyAll"/>
  1247. </http>
  1248. <bean id="dispatchers" class="org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher">
  1249. <constructor-arg>
  1250. <util:list value-type="javax.servlet.DispatcherType">
  1251. <value>FORWARD</value>
  1252. <value>INCLUDE</value>
  1253. </util:list>
  1254. </constructor-arg>
  1255. </bean>
  1256. ----
  1257. ====
  1258. 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`:
  1259. ====
  1260. .Java
  1261. [source,java,role="primary"]
  1262. ----
  1263. http
  1264. .authorizeHttpRequests((authorize) -> authorize
  1265. .filterAllDispatcherTypes(false)
  1266. .mvcMatchers("/app/**").hasRole("APP")
  1267. // ...
  1268. )
  1269. // ...
  1270. ----
  1271. .Kotlin
  1272. [source,kotlin,role="secondary"]
  1273. ----
  1274. http {
  1275. authorizeHttpRequests {
  1276. filterAllDispatcherTypes = false
  1277. authorize("/messages/**", hasRole("APP"))
  1278. // ...
  1279. }
  1280. }
  1281. ----
  1282. .Xml
  1283. [source,xml,role="secondary"]
  1284. ----
  1285. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  1286. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1287. <!-- ... -->
  1288. </http>
  1289. ----
  1290. ====
  1291. or, if you are still using `authorizeRequests` or `use-authorization-manager="false"`, set `oncePerRequest` to `true`:
  1292. ====
  1293. .Java
  1294. [source,java,role="primary"]
  1295. ----
  1296. http
  1297. .authorizeRequests((authorize) -> authorize
  1298. .filterSecurityInterceptorOncePerRequest(true)
  1299. .mvcMatchers("/app/**").hasRole("APP")
  1300. // ...
  1301. )
  1302. // ...
  1303. ----
  1304. .Kotlin
  1305. [source,kotlin,role="secondary"]
  1306. ----
  1307. http {
  1308. authorizeRequests {
  1309. filterSecurityInterceptorOncePerRequest = true
  1310. authorize("/messages/**", hasRole("APP"))
  1311. // ...
  1312. }
  1313. }
  1314. ----
  1315. .Xml
  1316. [source,xml,role="secondary"]
  1317. ----
  1318. <http once-per-request="true" use-authorization-manager="false">
  1319. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1320. <!-- ... -->
  1321. </http>
  1322. ----
  1323. ====
  1324. ===== I cannot declare an authorization rule for all requests
  1325. 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:
  1326. ====
  1327. .Java
  1328. [source,java,role="primary"]
  1329. ----
  1330. http
  1331. .authorizeHttpReqeusts((authorize) -> authorize
  1332. .mvcMatchers("/app/*").hasRole("APP")
  1333. // ...
  1334. .anyRequest().permitAll()
  1335. )
  1336. ----
  1337. .Kotlin
  1338. [source,kotlin,role="secondary"]
  1339. ----
  1340. http {
  1341. authorizeHttpRequests {
  1342. authorize("/app*", hasRole("APP"))
  1343. // ...
  1344. authorize(anyRequest, permitAll)
  1345. }
  1346. }
  1347. ----
  1348. .Xml
  1349. [source,xml,role="secondary"]
  1350. ----
  1351. <http>
  1352. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1353. <!-- ... -->
  1354. <intercept-url pattern="/**" access="permitAll"/>
  1355. </http>
  1356. ----
  1357. ====
  1358. ===== I cannot migrate my SpEL or my `AccessDecisionManager`
  1359. 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.
  1360. 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.
  1361. Second, if you still need your custom `access-decision-manager-ref` or have some other reason to opt out of `AuthorizationManager`, do:
  1362. ====
  1363. .Xml
  1364. [source,xml,role="secondary"]
  1365. ----
  1366. <http use-authorization-manager="false">
  1367. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1368. <!-- ... -->
  1369. </http>
  1370. ----
  1371. ====
  1372. === Propagate ``AuthenticationServiceException``s
  1373. {security-api-url}org/springframework/security/web/authentication/AuthenticationFilter.html[`AuthenticationFilter`] propagates {security-api-url}org/springframework/security/authentication/AuthenticationServiceException.html[``AuthenticationServiceException``]s to the {security-api-url}org/springframework/security/authentication/AuthenticationEntryPoint.html[`AuthenticationEntryPoint`].
  1374. Because ``AuthenticationServiceException``s represent a server-side error instead of a client-side error, in 6.0, this changes to propagate them to the container.
  1375. ==== Configure `AuthenticationFailureHandler` to rethrow ``AuthenticationServiceException``s
  1376. To prepare for the 6.0 default, wire `AuthenticationFilter` instances with a `AuthenticationFailureHandler` that rethrows ``AuthenticationServiceException``s, like so:
  1377. ====
  1378. .Java
  1379. [source,java,role="primary"]
  1380. ----
  1381. AuthenticationFilter authenticationFilter = new AuthenticationFilter(...);
  1382. AuthenticationEntryPointFailureHandler handler = new AuthenticationEntryPointFailureHandler(...);
  1383. handler.setRethrowAuthenticationServiceException(true);
  1384. authenticationFilter.setAuthenticationFailureHandler(handler);
  1385. ----
  1386. .Kotlin
  1387. [source,kotlin,role="secondary"]
  1388. ----
  1389. val authenticationFilter: AuthenticationFilter = new AuthenticationFilter(...)
  1390. val handler: AuthenticationEntryPointFailureHandler = new AuthenticationEntryPointFailureHandler(...)
  1391. handler.setRethrowAuthenticationServiceException(true)
  1392. authenticationFilter.setAuthenticationFailureHandler(handler)
  1393. ----
  1394. .Xml
  1395. [source,xml,role="secondary"]
  1396. ----
  1397. <bean id="authenticationFilter" class="org.springframework.security.web.authentication.AuthenticationFilter">
  1398. <!-- ... -->
  1399. <property ref="authenticationFailureHandler"/>
  1400. </bean>
  1401. <bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler">
  1402. <property name="rethrowAuthenticationServiceException" value="true"/>
  1403. </bean>
  1404. ----
  1405. ====
  1406. [[servlet-authenticationfailurehandler-opt-out]]
  1407. ==== Opt-out Steps
  1408. If rethrowing ``AuthenticationServiceException``s gives you trouble, you can set the value to false instead of taking the 6.0 default, like so:
  1409. ====
  1410. .Java
  1411. [source,java,role="primary"]
  1412. ----
  1413. AuthenticationFilter authenticationFilter = new AuthenticationFilter(...);
  1414. AuthenticationEntryPointFailureHandler handler = new AuthenticationEntryPointFailureHandler(...);
  1415. handler.setRethrowAuthenticationServiceException(false);
  1416. authenticationFilter.setAuthenticationFailureHandler(handler);
  1417. ----
  1418. .Kotlin
  1419. [source,kotlin,role="secondary"]
  1420. ----
  1421. val authenticationFilter: AuthenticationFilter = new AuthenticationFilter(...)
  1422. val handler: AuthenticationEntryPointFailureHandler = new AuthenticationEntryPointFailureHandler(...)
  1423. handler.setRethrowAuthenticationServiceException(false)
  1424. authenticationFilter.setAuthenticationFailureHandler(handler)
  1425. ----
  1426. .Xml
  1427. [source,xml,role="secondary"]
  1428. ----
  1429. <bean id="authenticationFilter" class="org.springframework.security.web.authentication.AuthenticationFilter">
  1430. <!-- ... -->
  1431. <property ref="authenticationFailureHandler"/>
  1432. </bean>
  1433. <bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler">
  1434. <property name="rethrowAuthenticationServiceException" value="false"/>
  1435. </bean>
  1436. ----
  1437. ====
  1438. [[servlet-opt-in-sha256-rememberme]]
  1439. === Use SHA-256 in Remember Me
  1440. The `TokenBasedRememberMeServices` implementation now supports SHA-256 for the Remember Me token and this is the default in Spring Security 6.
  1441. This change makes the implementation more secure by default since MD5 is already proven to be a weak hashing algorithm and vulnerable against collision attacks and modular differential attacks.
  1442. The new generated tokens now have the information of which algorithm was used to generate the token and that information is used in order to match it.
  1443. If the algorithm name is not present, then the `matchingAlgorithm` property is used to check the token.
  1444. This allows for a smooth transition from MD5 to SHA-256.
  1445. To opt into the new Spring Security 6 default to encode the tokens while still being able to decode tokens encoded with MD5, you can set the `encodingAlgorithm` property to SHA-256 and the `matchingAlgorithm` property to MD5.
  1446. See the xref:servlet/authentication/rememberme.adoc#_tokenbasedremembermeservices[reference documentation] and the {security-api-url}org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.html[API docs] for more information.
  1447. [[servlet-opt-in-sha256-sha256-encoding]]
  1448. .Use Spring Security 6 defaults for encoding, SHA-256 for encoding and MD5 for matching
  1449. ====
  1450. .Java
  1451. [source,java,role="primary"]
  1452. ----
  1453. @Configuration
  1454. @EnableWebSecurity
  1455. public class SecurityConfig {
  1456. @Bean
  1457. SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
  1458. http
  1459. // ...
  1460. .rememberMe((remember) -> remember
  1461. .rememberMeServices(rememberMeServices)
  1462. );
  1463. return http.build();
  1464. }
  1465. @Bean
  1466. RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
  1467. RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.SHA256;
  1468. TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm);
  1469. rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
  1470. return rememberMe;
  1471. }
  1472. }
  1473. ----
  1474. .XML
  1475. [source,xml,role="secondary"]
  1476. ----
  1477. <http>
  1478. <remember-me services-ref="rememberMeServices"/>
  1479. </http>
  1480. <bean id="rememberMeServices" class=
  1481. "org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
  1482. <property name="userDetailsService" ref="myUserDetailsService"/>
  1483. <property name="key" value="springRocks"/>
  1484. <property name="matchingAlgorithm" value="MD5"/>
  1485. <property name="encodingAlgorithm" value="SHA256"/>
  1486. </bean>
  1487. ----
  1488. ====
  1489. At some point, you will want to fully migrate to Spring Security 6 defaults. But how do you know when it is safe to do so?
  1490. Let's suppose that you deployed your application using SHA-256 as the encoding algorithm (as you have done <<servlet-opt-in-sha256-sha256-encoding,here>>) on November 1st, if you have the value for the `tokenValiditySeconds` property set to N days (14 is the default), you can migrate to SHA-256 N days after November 1st (which is November 15th in this example).
  1491. By that time, all the tokens generated with MD5 will have expired.
  1492. .Use Spring Security 6 defaults, SHA-256 for both encoding and matching
  1493. ====
  1494. .Java
  1495. [source,java,role="primary"]
  1496. ----
  1497. @Configuration
  1498. @EnableWebSecurity
  1499. public class SecurityConfig {
  1500. @Bean
  1501. SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
  1502. http
  1503. // ...
  1504. .rememberMe((remember) -> remember
  1505. .rememberMeServices(rememberMeServices)
  1506. );
  1507. return http.build();
  1508. }
  1509. @Bean
  1510. RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
  1511. RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.SHA256;
  1512. TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm);
  1513. rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.SHA256);
  1514. return rememberMe;
  1515. }
  1516. }
  1517. ----
  1518. .XML
  1519. [source,xml,role="secondary"]
  1520. ----
  1521. <http>
  1522. <remember-me services-ref="rememberMeServices"/>
  1523. </http>
  1524. <bean id="rememberMeServices" class=
  1525. "org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
  1526. <property name="userDetailsService" ref="myUserDetailsService"/>
  1527. <property name="key" value="springRocks"/>
  1528. <property name="matchingAlgorithm" value="SHA256"/>
  1529. <property name="encodingAlgorithm" value="SHA256"/>
  1530. </bean>
  1531. ----
  1532. ====
  1533. If you are having problems with the Spring Security 6 defaults, you can explicitly opt into 5.8 defaults using the following configuration:
  1534. .Use MD5 for both encoding and matching algorithms
  1535. ====
  1536. .Java
  1537. [source,java,role="primary"]
  1538. ----
  1539. @Configuration
  1540. @EnableWebSecurity
  1541. public class SecurityConfig {
  1542. @Bean
  1543. SecurityFilterChain securityFilterChain(HttpSecurity http, RememberMeServices rememberMeServices) throws Exception {
  1544. http
  1545. // ...
  1546. .rememberMe((remember) -> remember
  1547. .rememberMeServices(rememberMeServices)
  1548. );
  1549. return http.build();
  1550. }
  1551. @Bean
  1552. RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
  1553. RememberMeTokenAlgorithm encodingAlgorithm = RememberMeTokenAlgorithm.MD5;
  1554. TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices(myKey, userDetailsService, encodingAlgorithm);
  1555. rememberMe.setMatchingAlgorithm(RememberMeTokenAlgorithm.MD5);
  1556. return rememberMe;
  1557. }
  1558. }
  1559. ----
  1560. .XML
  1561. [source,xml,role="secondary"]
  1562. ----
  1563. <http>
  1564. <remember-me services-ref="rememberMeServices"/>
  1565. </http>
  1566. <bean id="rememberMeServices" class=
  1567. "org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
  1568. <property name="userDetailsService" ref="myUserDetailsService"/>
  1569. <property name="key" value="springRocks"/>
  1570. <property name="matchingAlgorithm" value="MD5"/>
  1571. <property name="encodingAlgorithm" value="MD5"/>
  1572. </bean>
  1573. ----
  1574. ====
  1575. === Stop Using SAML 2.0 `Converter` constructors
  1576. In an early release of Spring Security's SAML 2.0 support, `Saml2MetadataFilter` and `Saml2AuthenticationTokenConverter` shipped with constructors of type `Converter`.
  1577. This level of abstraction made it tricky to evolve the class and so a dedicated interface `RelyingPartyRegistrationResolver` was introduced in a later release.
  1578. In 6.0, the `Converter` constructors are removed.
  1579. To prepare for this in 5.8, change classes that implement `Converter<HttpServletRequest, RelyingPartyRegistration>` to instead implement `RelyingPartyRegistrationResolver`.
  1580. === Change to Using `Saml2AuthenticationRequestResolver`
  1581. `Saml2AuthenticationContextResolver` and `Saml2AuthenticationRequestFactory` are removed in 6.0 as is the `Saml2WebSsoAuthenticationRequestFilter` that requires them.
  1582. They are replaced by `Saml2AuthenticationRequestResolver` and a new constructor in `Saml2WebSsoAuthenticationRequestFilter`.
  1583. The new interface removes an unnecessary transport object between the two classes.
  1584. Most applications need do nothing; however, if you use or configure `Saml2AuthenticationRequestContextResolver` or `Saml2AuthenticationRequestFactory`, try the following steps to convert instead use `Saml2AuthenticationRequestResolver`.
  1585. ==== Use `setAuthnRequestCustomizer` instead of `setAuthenticationRequestContextConverter`
  1586. If you are calling `OpenSaml4AuthenticationReqeustFactory#setAuthenticationRequestContextConverter`, for example, like so:
  1587. ====
  1588. .Java
  1589. [source,java,role="primary"]
  1590. ----
  1591. @Bean
  1592. Saml2AuthenticationRequestFactory authenticationRequestFactory() {
  1593. OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory();
  1594. factory.setAuthenticationRequestContextConverter((context) -> {
  1595. AuthnRequestBuilder authnRequestBuilder = ConfigurationService.get(XMLObjectProviderRegistry.class)
  1596. .getBuilderFactory().getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
  1597. IssuerBuilder issuerBuilder = ConfigurationService.get(XMLObjectProviderRegistry.class)
  1598. .getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
  1599. tring issuer = context.getIssuer();
  1600. String destination = context.getDestination();
  1601. String assertionConsumerServiceUrl = context.getAssertionConsumerServiceUrl();
  1602. String protocolBinding = context.getRelyingPartyRegistration().getAssertionConsumerServiceBinding().getUrn();
  1603. AuthnRequest auth = authnRequestBuilder.buildObject();
  1604. auth.setID("ARQ" + UUID.randomUUID().toString().substring(1));
  1605. auth.setIssueInstant(Instant.now());
  1606. auth.setForceAuthn(Boolean.TRUE);
  1607. auth.setIsPassive(Boolean.FALSE);
  1608. auth.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
  1609. Issuer iss = issuerBuilder.buildObject();
  1610. iss.setValue(issuer);
  1611. auth.setIssuer(iss);
  1612. auth.setDestination(destination);
  1613. auth.setAssertionConsumerServiceURL(assertionConsumerServiceUrl);
  1614. });
  1615. return factory;
  1616. }
  1617. ----
  1618. ====
  1619. to ensure that ForceAuthn is set to `true`, you can instead do:
  1620. ====
  1621. .Java
  1622. [source,java,role="primary"]
  1623. ----
  1624. @Bean
  1625. Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyRegistrationResolver registrations) {
  1626. OpenSaml4AuthenticationRequestResolver reaolver = new OpenSaml4AuthenticationRequestResolver(registrations);
  1627. resolver.setAuthnRequestCustomizer((context) -> context.getAuthnRequest().setForceAuthn(Boolean.TRUE));
  1628. return resolver;
  1629. }
  1630. ----
  1631. ====
  1632. Also, since `setAuthnRequestCustomizer` has direct access to the `HttpServletRequest`, there is no need for a `Saml2AuthenticationRequestContextResolver`.
  1633. Simply use `setAuthnRequestCustomizer` to read directly from `HttpServletRequest` this information you need.
  1634. ==== Use `setAuthnRequestCustomizer` instead of `setProtocolBinding`
  1635. Instead of doing:
  1636. ====
  1637. .Java
  1638. [source,java,role="primary"]
  1639. ----
  1640. @Bean
  1641. Saml2AuthenticationRequestFactory authenticationRequestFactory() {
  1642. OpenSaml4AuthenticationRequestFactory factory = new OpenSaml4AuthenticationRequestFactory();
  1643. factory.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
  1644. return factory;
  1645. }
  1646. ----
  1647. ====
  1648. you can do:
  1649. ====
  1650. .Java
  1651. [source,java,role="primary"]
  1652. ----
  1653. @Bean
  1654. Saml2AuthenticationRequestResolver authenticationRequestResolver() {
  1655. OpenSaml4AuthenticationRequestResolver reaolver = new OpenSaml4AuthenticationRequestResolver(registrations);
  1656. resolver.setAuthnRequestCustomizer((context) -> context.getAuthnRequest()
  1657. .setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"));
  1658. return resolver;
  1659. }
  1660. ----
  1661. ====
  1662. [NOTE]
  1663. ====
  1664. Since Spring Security only supports the `POST` binding for authentication, there is not very much value in overriding the protocol binding at this point in time.
  1665. ====
  1666. === Use the latest `Saml2AuthenticationToken` constructor
  1667. In an early release, `Saml2AuthenticationToken` took several individual settings as constructor parameters.
  1668. This created a challenge each time a new parameter needed to be added.
  1669. Since most of these settings were part of `RelyingPartyRegistration`, a new constructor was added where a `RelyingPartyRegistration` could be provided, making the constructor more stable.
  1670. It also is valuable in that it more closely aligns with the design of `OAuth2LoginAuthenticationToken`.
  1671. Most applications do not construct this class directly since `Saml2WebSsoAuthenticationFilter` does.
  1672. However, in the event that your application constructs one, please change from:
  1673. ====
  1674. .Java
  1675. [source,java,role="primary"]
  1676. ----
  1677. new Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),
  1678. registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())
  1679. ----
  1680. .Kotlin
  1681. [source,kotlin,role="secondary"]
  1682. ----
  1683. Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(),
  1684. registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials())
  1685. ----
  1686. ====
  1687. to:
  1688. ====
  1689. .Java
  1690. [source,java,role="primary"]
  1691. ----
  1692. new Saml2AuthenticationToken(saml2Response, registration)
  1693. ----
  1694. .Kotlin
  1695. [source,kotlin,role="secondary"]
  1696. ----
  1697. Saml2AuthenticationToken(saml2Response, registration)
  1698. ----
  1699. ====
  1700. === Use `RelyingPartyRegistration` updated methods
  1701. In an early release of Spring Security's SAML support, there was some ambiguity on the meaning of certain `RelyingPartyRegistration` methods and their function.
  1702. As more capabilities were added to `RelyingPartyRegistration`, it became necessary to clarify this ambiguity by changing method names to ones that aligned with spec language.
  1703. The deprecated methods in `RelyingPartyRegstration` are removed.
  1704. To prepare for that, consider the following representative usage of `RelyingPartyRegistration`:
  1705. ====
  1706. .Java
  1707. [source,java,role="primary"]
  1708. ----
  1709. String idpEntityId = registration.getRemoteIdpEntityId();
  1710. String assertionConsumerServiceUrl = registration.getAssertionConsumerServiceUrlTemplate();
  1711. String idpWebSsoUrl = registration.getIdpWebSsoUrl();
  1712. String localEntityId = registration.getLocalEntityIdTemplate();
  1713. List<Saml2X509Credential> verifying = registration.getCredentials().stream()
  1714. .filter(Saml2X509Credential::isSignatureVerficationCredential)
  1715. .collect(Collectors.toList());
  1716. ----
  1717. .Kotlin
  1718. [source,kotlin,role="secondary"]
  1719. ----
  1720. val idpEntityId: String = registration.getRemoteIdpEntityId()
  1721. val assertionConsumerServiceUrl: String = registration.getAssertionConsumerServiceUrlTemplate()
  1722. val idpWebSsoUrl: String = registration.getIdpWebSsoUrl()
  1723. val localEntityId: String = registration.getLocalEntityIdTemplate()
  1724. val verifying: List<Saml2X509Credential> = registration.getCredentials()
  1725. .filter(Saml2X509Credential::isSignatureVerficationCredential)
  1726. ----
  1727. ====
  1728. This should change to:
  1729. ====
  1730. .Java
  1731. [source,java,role="primary"]
  1732. ----
  1733. String assertingPartyEntityId = registration.getAssertingPartyDetails().getEntityId();
  1734. String assertionConsumerServiceLocation = registration.getAssertionConsumerServiceLocation();
  1735. String singleSignOnServiceLocation = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation();
  1736. String entityId = registration.getEntityId();
  1737. List<Saml2X509Credential> verifying = registration.getAssertingPartyDetails().getVerificationX509Credentials();
  1738. ----
  1739. .Kotlin
  1740. [source,kotlin,role="secondary"]
  1741. ----
  1742. val assertingPartyEntityId: String = registration.getAssertingPartyDetails().getEntityId()
  1743. val assertionConsumerServiceLocation: String = registration.getAssertionConsumerServiceLocation()
  1744. val singleSignOnServiceLocation: String = registration.getAssertingPartyDetails().getSingleSignOnServiceLocation()
  1745. val entityId: String = registration.getEntityId()
  1746. val verifying: List<Saml2X509Credential> = registration.getAssertingPartyDetails().getVerificationX509Credentials()
  1747. ----
  1748. ====
  1749. For a complete listing of all changed methods, please see {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[``RelyingPartyRegistration``'s JavaDoc].
  1750. == Reactive
  1751. === Use `AuthorizationManager` for Method Security
  1752. 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.
  1753. Should you run into trouble with making these changes, you can follow the
  1754. <<reactive-authorizationmanager-methods-opt-out,opt out steps>> at the end of this section.
  1755. 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.
  1756. [[reactive-change-to-useauthorizationmanager]]
  1757. ==== Change `useAuthorizationManager` to `true`
  1758. To opt in, change `useAuthorizationManager` to `true` like so:
  1759. ====
  1760. .Java
  1761. [source,java,role="primary"]
  1762. ----
  1763. @EnableReactiveMethodSecurity
  1764. ----
  1765. .Kotlin
  1766. [source,kotlin,role="secondary"]
  1767. ----
  1768. @EnableReactiveMethodSecurity
  1769. ----
  1770. ====
  1771. changes to:
  1772. ====
  1773. .Java
  1774. [source,java,role="primary"]
  1775. ----
  1776. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  1777. ----
  1778. .Kotlin
  1779. [source,kotlin,role="secondary"]
  1780. ----
  1781. @EnableReactiveMethodSecurity(useAuthorizationManager = true)
  1782. ----
  1783. ====
  1784. [[reactive-check-for-annotationconfigurationexceptions]]
  1785. ==== Check for ``AnnotationConfigurationException``s
  1786. `useAuthorizationManager` activates stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
  1787. 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.
  1788. [[reactive-authorizationmanager-methods-opt-out]]
  1789. ==== Opt-out Steps
  1790. If you ran into trouble with `AuthorizationManager` for reactive method security, you can opt out by changing:
  1791. ====
  1792. .Java
  1793. [source,java,role="primary"]
  1794. ----
  1795. @EnableReactiveMethodSecurity
  1796. ----
  1797. .Kotlin
  1798. [source,kotlin,role="secondary"]
  1799. ----
  1800. @EnableReactiveMethodSecurity
  1801. ----
  1802. ====
  1803. to:
  1804. ====
  1805. .Java
  1806. [source,java,role="primary"]
  1807. ----
  1808. @EnableReactiveMethodSecurity(useAuthorizationManager = false)
  1809. ----
  1810. .Kotlin
  1811. [source,kotlin,role="secondary"]
  1812. ----
  1813. @EnableReactiveMethodSecurity(useAuthorizationManager = false)
  1814. ----
  1815. ====
  1816. === Propagate ``AuthenticationServiceException``s
  1817. {security-api-url}org/springframework/security/web/server/Webauthentication/AuthenticationWebFilter.html[`AuthenticationFilter`] propagates {security-api-url}org/springframework/security/authentication/AuthenticationServiceException.html[``AuthenticationServiceException``]s to the {security-api-url}org/springframework/security/web/server/ServerAuthenticationEntryPoint.html[`ServerAuthenticationEntryPoint`].
  1818. Because ``AuthenticationServiceException``s represent a server-side error instead of a client-side error, in 6.0, this changes to propagate them to the container.
  1819. ==== Configure `ServerAuthenticationFailureHandler` to rethrow ``AuthenticationServiceException``s
  1820. To prepare for the 6.0 default, `httpBasic` and `oauth2ResourceServer` should be configured to rethrow ``AuthenticationServiceException``s.
  1821. For each, construct the appropriate authentication entry point for `httpBasic` and for `oauth2ResourceServer`:
  1822. ====
  1823. .Java
  1824. [source,java,role="primary"]
  1825. ----
  1826. ServerAuthenticationEntryPoint bearerEntryPoint = new BearerTokenServerAuthenticationEntryPoint();
  1827. ServerAuthenticationEntryPoint basicEntryPoint = new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED);
  1828. ----
  1829. .Kotlin
  1830. [source,kotlin,role="secondary"]
  1831. ----
  1832. val bearerEntryPoint: ServerAuthenticationEntryPoint = BearerTokenServerAuthenticationEntryPoint()
  1833. val basicEntryPoint: ServerAuthenticationEntryPoint = HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)
  1834. ----
  1835. ====
  1836. [NOTE]
  1837. ====
  1838. If you use a custom `AuthenticationEntryPoint` for either or both mechanisms, use that one instead for the remaining steps.
  1839. ====
  1840. Then, construct and configure a `ServerAuthenticationEntryPointFailureHandler` for each one:
  1841. ====
  1842. .Java
  1843. [source,java,role="primary"]
  1844. ----
  1845. AuthenticationFailureHandler bearerFailureHandler = new ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint);
  1846. bearerFailureHandler.setRethrowAuthenticationServiceException(true);
  1847. AuthenticationFailureHandler basicFailureHandler = new ServerAuthenticationEntryPointFailureHandler(basicEntryPoint);
  1848. basicFailureHandler.setRethrowAuthenticationServiceException(true)
  1849. ----
  1850. .Kotlin
  1851. [source,kotlin,role="secondary"]
  1852. ----
  1853. val bearerFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint)
  1854. bearerFailureHandler.setRethrowAuthenticationServiceException(true)
  1855. val basicFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(basicEntryPoint)
  1856. basicFailureHandler.setRethrowAuthenticationServiceException(true)
  1857. ----
  1858. ====
  1859. Finally, wire each authentication failure handler into the DSL, like so:
  1860. ====
  1861. .Java
  1862. [source,java,role="primary"]
  1863. ----
  1864. http
  1865. .httpBasic((basic) -> basic.authenticationFailureHandler(basicFailureHandler))
  1866. .oauth2ResourceServer((oauth2) -> oauth2.authenticationFailureHandler(bearerFailureHandler))
  1867. ----
  1868. .Kotlin
  1869. [source,kotlin,role="secondary"]
  1870. ----
  1871. http {
  1872. httpBasic {
  1873. authenticationFailureHandler = basicFailureHandler
  1874. }
  1875. oauth2ResourceServer {
  1876. authenticationFailureHandler = bearerFailureHandler
  1877. }
  1878. }
  1879. ----
  1880. ====
  1881. [[reactive-authenticationfailurehandler-opt-out]]
  1882. ==== Opt-out Steps
  1883. To opt-out of the 6.0 defaults and instead continue to pass `AuthenticationServiceException` on to ``ServerAuthenticationEntryPoint``s, you can follow the same steps as above, except set `rethrowAuthenticationServiceException` to false.