authorization.adoc 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737
  1. = Authorization Migrations
  2. The following steps relate to changes around how authorization is performed.
  3. == Use `AuthorizationManager` for Method Security
  4. 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.
  5. 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.
  6. [[servlet-replace-globalmethodsecurity-with-methodsecurity]]
  7. === 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]
  8. {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.
  9. 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.
  10. This means that the following two listings are functionally equivalent:
  11. ====
  12. .Java
  13. [source,java,role="primary"]
  14. ----
  15. @EnableGlobalMethodSecurity(prePostEnabled = true)
  16. ----
  17. .Kotlin
  18. [source,kotlin,role="secondary"]
  19. ----
  20. @EnableGlobalMethodSecurity(prePostEnabled = true)
  21. ----
  22. .Xml
  23. [source,xml,role="secondary"]
  24. ----
  25. <global-method-security pre-post-enabled="true"/>
  26. ----
  27. ====
  28. and:
  29. ====
  30. .Java
  31. [source,java,role="primary"]
  32. ----
  33. @EnableMethodSecurity
  34. ----
  35. .Kotlin
  36. [source,kotlin,role="secondary"]
  37. ----
  38. @EnableMethodSecurity
  39. ----
  40. .Xml
  41. [source,xml,role="secondary"]
  42. ----
  43. <method-security/>
  44. ----
  45. ====
  46. For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior.
  47. For example, a listing like:
  48. ====
  49. .Java
  50. [source,java,role="primary"]
  51. ----
  52. @EnableGlobalMethodSecurity(securedEnabled = true)
  53. ----
  54. .Kotlin
  55. [source,kotlin,role="secondary"]
  56. ----
  57. @EnableGlobalMethodSecurity(securedEnabled = true)
  58. ----
  59. .Xml
  60. [source,xml,role="secondary"]
  61. ----
  62. <global-method-security secured-enabled="true"/>
  63. ----
  64. ====
  65. should change to:
  66. ====
  67. .Java
  68. [source,java,role="primary"]
  69. ----
  70. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  71. ----
  72. .Kotlin
  73. [source,kotlin,role="secondary"]
  74. ----
  75. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  76. ----
  77. .Xml
  78. [source,xml,role="secondary"]
  79. ----
  80. <method-security secured-enabled="true" pre-post-enabled="false"/>
  81. ----
  82. ====
  83. === Change the `order` value in `@EnableTransactionManagement`
  84. `@EnableTransactionManagement` and `@EnableGlobalMethodSecurity` have the same `order` value, `Integer.MAX_VALUE`.
  85. This means that their order in the Spring AOP Advisor chain relative to each other is undefined.
  86. This is often fine since most method security expressions don't require an open transaction to function correctly; however, historically it was sometimes necessary to ensure one happens before the other by setting their `order` values.
  87. `@EnableMethodSecurity` does not have an `order` value since it publishes multiple interceptors.
  88. Indeed, it cannot attempt backward-compatibility with `@EnableTransactionManagement` since it cannot set all the interceptors to be in the same advisor chain location.
  89. Instead, the values for the `@EnableMethodSecurity` interceptors are based off of an offset of 0.
  90. The `@PreFilter` interceptor has an order of 100; `@PostAuthorize`, 200; and so on.
  91. So, if after updating you find that your method security expressions are not working due to not having an open transaction, please change your transaction annotation definition from the following:
  92. ====
  93. .Java
  94. [source,java,role="primary"]
  95. ----
  96. @EnableTransactionManagement
  97. ----
  98. .Kotlin
  99. [source,kotlin,role="secondary"]
  100. ----
  101. @EnableTransactionManagement
  102. ----
  103. .Xml
  104. [source,xml,role="secondary"]
  105. ----
  106. <tx:annotation-driven ref="txManager"/>
  107. ----
  108. ====
  109. to:
  110. ====
  111. .Java
  112. [source,java,role="primary"]
  113. ----
  114. @EnableTransactionManagement(order = 0)
  115. ----
  116. .Kotlin
  117. [source,kotlin,role="secondary"]
  118. ----
  119. @EnableTransactionManagement(order = 0)
  120. ----
  121. .Xml
  122. [source,xml,role="secondary"]
  123. ----
  124. <tx:annotation-driven ref="txManager" order="0"/>
  125. ----
  126. ====
  127. In this way, the transaction AOP advice will be placed before Spring Security's advice and the transaction will be open when your authorization SpEL expressions are evaluated.
  128. === Use a Custom `@Bean` instead of subclassing `DefaultMethodSecurityExpressionHandler`
  129. As a performance optimization, a new method was introduced to `MethodSecurityExpressionHandler` that takes a `Supplier<Authentication>` instead of an `Authentication`.
  130. This allows Spring Security to defer the lookup of the `Authentication`, and is taken advantage of automatically when you use `@EnableMethodSecurity` instead of `@EnableGlobalMethodSecurity`.
  131. However, let's say that your code extends `DefaultMethodSecurityExpressionHandler` and overrides `createSecurityExpressionRoot(Authentication, MethodInvocation)` to return a custom `SecurityExpressionRoot` instance.
  132. This will no longer work because the arrangement that `@EnableMethodSecurity` sets up calls `createEvaluationContext(Supplier<Authentication>, MethodInvocation)` instead.
  133. Happily, such a level of customization is often unnecessary.
  134. Instead, you can create a custom bean with the authorization methods that you need.
  135. For example, let's say you are wanting a custom evaluation of `@PostAuthorize("hasAuthority('ADMIN')")`.
  136. You can create a custom `@Bean` like this one:
  137. ====
  138. .Java
  139. [source,java,role="primary"]
  140. ----
  141. class MyAuthorizer {
  142. boolean isAdmin(MethodSecurityExpressionOperations root) {
  143. boolean decision = root.hasAuthority("ADMIN");
  144. // custom work ...
  145. return decision;
  146. }
  147. }
  148. ----
  149. .Kotlin
  150. [source,kotlin,role="secondary"]
  151. ----
  152. class MyAuthorizer {
  153. fun isAdmin(val root: MethodSecurityExpressionOperations): boolean {
  154. val decision = root.hasAuthority("ADMIN");
  155. // custom work ...
  156. return decision;
  157. }
  158. }
  159. ----
  160. ====
  161. and then refer to it in the annotation like so:
  162. ====
  163. .Java
  164. [source,java,role="primary"]
  165. ----
  166. @PreAuthorize("@authz.isAdmin(#root)")
  167. ----
  168. .Kotlin
  169. [source,kotlin,role="secondary"]
  170. ----
  171. @PreAuthorize("@authz.isAdmin(#root)")
  172. ----
  173. ====
  174. ==== I'd still prefer to subclass `DefaultMethodSecurityExpressionHandler`
  175. If you must continue subclassing `DefaultMethodSecurityExpressionHandler`, you can still do so.
  176. Instead, override the `createEvaluationContext(Supplier<Authentication>, MethodInvocation)` method like so:
  177. ====
  178. .Java
  179. [source,java,role="primary"]
  180. ----
  181. @Component
  182. class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
  183. @Override
  184. public EvaluationContext createEvaluationContext(
  185. Supplier<Authentication> authentication, MethodInvocation mi) {
  186. StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
  187. MySecurityExpressionRoot root = new MySecurityExpressionRoot(authentication, invocation);
  188. root.setPermissionEvaluator(getPermissionEvaluator());
  189. root.setTrustResolver(new AuthenticationTrustResolverImpl());
  190. root.setRoleHierarchy(getRoleHierarchy());
  191. context.setRootObject(root);
  192. return context;
  193. }
  194. }
  195. ----
  196. .Kotlin
  197. [source,kotlin,role="secondary"]
  198. ----
  199. @Component
  200. class MyExpressionHandler: DefaultMethodSecurityExpressionHandler {
  201. override fun createEvaluationContext(val authentication: Supplier<Authentication>,
  202. val mi: MethodInvocation): EvaluationContext {
  203. val context = super.createEvaluationContext(authentication, mi) as StandardEvaluationContext;
  204. val root = new MySecurityExpressionRoot(authentication, invocation);
  205. root.setPermissionEvaluator(getPermissionEvaluator());
  206. root.setTrustResolver(new AuthenticationTrustResolverImpl());
  207. root.setRoleHierarchy(getRoleHierarchy());
  208. context.setRootObject(root);
  209. return context;
  210. }
  211. }
  212. ----
  213. ====
  214. ==== Opt-out Steps
  215. If you need to opt-out of these changes, you can use `@EnableGlobalMethodSecurity` instead of `@EnableMethodSecurity`
  216. [[servlet-replace-permissionevaluator-bean-with-methodsecurityexpression-handler]]
  217. === Publish a `MethodSecurityExpressionHandler` instead of a `PermissionEvaluator`
  218. `@EnableMethodSecurity` does not pick up a `PermissionEvaluator`.
  219. This helps keep its API simple.
  220. If you have a custom {security-api-url}org/springframework/security/access/PermissionEvaluator.html[`PermissionEvaluator`] `@Bean`, please change it from:
  221. ====
  222. .Java
  223. [source,java,role="primary"]
  224. ----
  225. @Bean
  226. static PermissionEvaluator permissionEvaluator() {
  227. // ... your evaluator
  228. }
  229. ----
  230. .Kotlin
  231. [source,kotlin,role="secondary"]
  232. ----
  233. companion object {
  234. @Bean
  235. fun permissionEvaluator(): PermissionEvaluator {
  236. // ... your evaluator
  237. }
  238. }
  239. ----
  240. ====
  241. to:
  242. ====
  243. .Java
  244. [source,java,role="primary"]
  245. ----
  246. @Bean
  247. static MethodSecurityExpressionHandler expressionHandler() {
  248. var expressionHandler = new DefaultMethodSecurityExpressionHandler();
  249. expressionHandler.setPermissionEvaluator(myPermissionEvaluator);
  250. return expressionHandler;
  251. }
  252. ----
  253. .Kotlin
  254. [source,kotlin,role="secondary"]
  255. ----
  256. companion object {
  257. @Bean
  258. fun expressionHandler(): MethodSecurityExpressionHandler {
  259. val expressionHandler = DefaultMethodSecurityExpressionHandler
  260. expressionHandler.setPermissionEvaluator(myPermissionEvaluator)
  261. return expressionHandler
  262. }
  263. }
  264. ----
  265. ====
  266. === Replace any custom method-security ``AccessDecisionManager``s
  267. 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.
  268. The preparation strategy will depend on your reason for each arrangement.
  269. Read on to find the best match for your situation.
  270. ==== I use `UnanimousBased`
  271. 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`].
  272. However, if you do discover that you cannot accept the default authorization managers, you can use `AuthorizationManagers.allOf` to compose your own arrangement.
  273. Note that there is a difference with `allOf`, which is that if all delegates abstain then it grants authorization.
  274. If you must deny authorization when all delegates abstain, please implement a composite {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] that takes the set of delegate ``AuthorizationManager``s into account.
  275. 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`].
  276. ==== I use `AffirmativeBased`
  277. 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:
  278. ====
  279. .Java
  280. [source,java,role="primary"]
  281. ----
  282. AuthorizationManager<MethodInvocation> authorization = AuthorizationManagers.anyOf(
  283. // ... your list of authorization managers
  284. )
  285. ----
  286. .Kotlin
  287. [source,kotlin,role="secondary"]
  288. ----
  289. val authorization = AuthorizationManagers.anyOf(
  290. // ... your list of authorization managers
  291. )
  292. ----
  293. ====
  294. 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`].
  295. ==== I use `ConsensusBased`
  296. There is no framework-provided equivalent for {security-api-url}org/springframework/security/access/vote/ConsensusBased.html[`ConsensusBased`].
  297. 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.
  298. 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`].
  299. ==== I use a custom `AccessDecisionVoter`
  300. You should either change the class to implement {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] or create an adapter.
  301. Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution.
  302. 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:
  303. ====
  304. .Java
  305. [source,java,role="primary"]
  306. ----
  307. public final class PreAuthorizeAuthorizationManagerAdapter implements AuthorizationManager<MethodInvocation> {
  308. private final SecurityMetadataSource metadata;
  309. private final AccessDecisionVoter voter;
  310. public PreAuthorizeAuthorizationManagerAdapter(MethodSecurityExpressionHandler expressionHandler) {
  311. ExpressionBasedAnnotationAttributeFactory attributeFactory =
  312. new ExpressionBasedAnnotationAttributeFactory(expressionHandler);
  313. this.metadata = new PrePostAnnotationSecurityMetadataSource(attributeFactory);
  314. ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
  315. expressionAdvice.setExpressionHandler(expressionHandler);
  316. this.voter = new PreInvocationAuthorizationAdviceVoter(expressionAdvice);
  317. }
  318. public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
  319. List<ConfigAttribute> attributes = this.metadata.getAttributes(invocation, AopUtils.getTargetClass(invocation.getThis()));
  320. int decision = this.voter.vote(authentication.get(), invocation, attributes);
  321. if (decision == ACCESS_GRANTED) {
  322. return new AuthorizationDecision(true);
  323. }
  324. if (decision == ACCESS_DENIED) {
  325. return new AuthorizationDecision(false);
  326. }
  327. return null; // abstain
  328. }
  329. }
  330. ----
  331. ====
  332. 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`].
  333. ==== I use `AfterInvocationManager` or `AfterInvocationProvider`
  334. {security-api-url}org/springframework/security/access/intercept/AfterInvocationManager.html;[`AfterInvocationManager`] and {security-api-url}org/springframework/security/access/intercept/AfterInvocationProvider.html[`AfterInvocationProvider`] make an authorization decision about an invocation's result.
  335. For example, in the case of method invocation, these make an authorization decision about a method's return value.
  336. In Spring Security 3.0, authorization decision-making was standardized into the xref:servlet/authorization/method-security.adoc[`@PostAuthorize` and `@PostFilter` annotations].
  337. `@PostAuthorize` is for deciding whether the return value as a whole was permitted to be returned.
  338. `@PostFilter` is for filtering individual entries from a returned collection, array, or stream.
  339. These two annotations should serve most needs, and you are encouraged to migrate to one or both of them since `AfterInvocationProvider` and `AfterInvocationManager` are now deprecated.
  340. If you've implemented your own `AfterInvocationManager` or `AfterInvocationProvider`, you should first ask yourself what it is trying to do.
  341. If it is trying to authorize the return type, <<_i_use_a_custom_accessdecisionvoter,consider implementing `AuthorizationManager<MethodInvocationResult>` and using `AfterMethodAuthorizationManagerInterceptor`>>. Or publishing a custom bean and using `@PostAuthorize("@myBean.authorize(#root)")`.
  342. If it is trying to filter, then consider publishing a custom bean and using `@PostFilter("@mybean.authorize(#root)")`.
  343. Or, if needed, you can implement your own `MethodInterceptor`, taking a look at `PostFilterAuthorizationMethodInterceptor` and `PrePostMethodSecurityConfiguration` for an example.
  344. ==== I use `RunAsManager`
  345. There is currently https://github.com/spring-projects/spring-security/issues/11331[no replacement for `RunAsManager`] though one is being considered.
  346. It is quite straightforward to adapt a `RunAsManager`, though, to the `AuthorizationManager` API, if needed.
  347. Here is some pseudocode to get you started:
  348. ====
  349. .Java
  350. [source,java,role="primary"]
  351. ----
  352. public final class RunAsAuthorizationManagerAdapter<T> implements AuthorizationManager<T> {
  353. private final RunAsManager runAs = new RunAsManagerImpl();
  354. private final SecurityMetadataSource metadata;
  355. private final AuthorizationManager<T> authorization;
  356. // ... constructor
  357. public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
  358. Supplier<Authentication> wrapped = (auth) -> {
  359. List<ConfigAttribute> attributes = this.metadata.getAttributes(object);
  360. return this.runAs.buildRunAs(auth, object, attributes);
  361. };
  362. return this.authorization.check(wrapped, object);
  363. }
  364. }
  365. ----
  366. ====
  367. 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`].
  368. [[servlet-check-for-annotationconfigurationexceptions]]
  369. === Check for ``AnnotationConfigurationException``s
  370. `@EnableMethodSecurity` and `<method-security>` activate stricter enforcement of Spring Security's non-repeatable or otherwise incompatible annotations.
  371. 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.
  372. == Use `AuthorizationManager` for Message Security
  373. 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.
  374. 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.
  375. === Ensure all messages have defined authorization rules
  376. The now-deprecated {security-api-url}org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.html[message security support] permits all messages by default.
  377. xref:servlet/integrations/websocket.adoc[The new support] has the stronger default of denying all messages.
  378. To prepare for this, ensure that authorization rules exist are declared for every request.
  379. For example, an application configuration like:
  380. ====
  381. .Java
  382. [source,java,role="primary"]
  383. ----
  384. @Override
  385. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  386. messages
  387. .simpDestMatchers("/user/queue/errors").permitAll()
  388. .simpDestMatchers("/admin/**").hasRole("ADMIN");
  389. }
  390. ----
  391. .Kotlin
  392. [source,kotlin,role="secondary"]
  393. ----
  394. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  395. messages
  396. .simpDestMatchers("/user/queue/errors").permitAll()
  397. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  398. }
  399. ----
  400. .Xml
  401. [source,xml,role="secondary"]
  402. ----
  403. <websocket-message-broker>
  404. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  405. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  406. </websocket-message-broker>
  407. ----
  408. ====
  409. should change to:
  410. ====
  411. .Java
  412. [source,java,role="primary"]
  413. ----
  414. @Override
  415. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  416. messages
  417. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  418. .simpDestMatchers("/user/queue/errors").permitAll()
  419. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  420. .anyMessage().denyAll();
  421. }
  422. ----
  423. .Kotlin
  424. [source,kotlin,role="secondary"]
  425. ----
  426. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  427. messages
  428. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  429. .simpDestMatchers("/user/queue/errors").permitAll()
  430. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  431. .anyMessage().denyAll()
  432. }
  433. ----
  434. .Xml
  435. [source,xml,role="secondary"]
  436. ----
  437. <websocket-message-broker>
  438. <intercept-message type="CONNECT" access="permitAll"/>
  439. <intercept-message type="DISCONNECT" access="permitAll"/>
  440. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  441. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  442. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  443. <intercept-message pattern="/**" access="denyAll"/>
  444. </websocket-message-broker>
  445. ----
  446. ====
  447. === Add `@EnableWebSocketSecurity`
  448. [NOTE]
  449. ====
  450. If you want to have CSRF disabled and you are using Java configuration, the migration steps are slightly different.
  451. Instead of using `@EnableWebSocketSecurity`, you will override the appropriate methods in `WebSocketMessageBrokerConfigurer` yourself.
  452. Please see xref:servlet/integrations/websocket.adoc#websocket-sameorigin-disable[the reference manual] for details about this step.
  453. ====
  454. If you are using Java Configuration, add {security-api-url}org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.html[`@EnableWebSocketSecurity`] to your application.
  455. For example, you can add it to your websocket security configuration class, like so:
  456. ====
  457. .Java
  458. [source,java,role="primary"]
  459. ----
  460. @EnableWebSocketSecurity
  461. @Configuration
  462. public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
  463. // ...
  464. }
  465. ----
  466. .Kotlin
  467. [source,kotlin,role="secondary"]
  468. ----
  469. @EnableWebSocketSecurity
  470. @Configuration
  471. class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
  472. // ...
  473. }
  474. ----
  475. ====
  476. This will make a prototype instance of `MessageMatcherDelegatingAuthorizationManager.Builder` available to encourage configuration by composition instead of extension.
  477. === Use an `AuthorizationManager<Message<?>>` instance
  478. To start using `AuthorizationManager`, you can set the `use-authorization-manager` attribute in XML or you can publish an `AuthorizationManager<Message<?>>` `@Bean` in Java.
  479. For example, the following application configuration:
  480. ====
  481. .Java
  482. [source,java,role="primary"]
  483. ----
  484. @Override
  485. protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
  486. messages
  487. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  488. .simpDestMatchers("/user/queue/errors").permitAll()
  489. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  490. .anyMessage().denyAll();
  491. }
  492. ----
  493. .Kotlin
  494. [source,kotlin,role="secondary"]
  495. ----
  496. override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
  497. messages
  498. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  499. .simpDestMatchers("/user/queue/errors").permitAll()
  500. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  501. .anyMessage().denyAll()
  502. }
  503. ----
  504. .Xml
  505. [source,xml,role="secondary"]
  506. ----
  507. <websocket-message-broker>
  508. <intercept-message type="CONNECT" access="permitAll"/>
  509. <intercept-message type="DISCONNECT" access="permitAll"/>
  510. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  511. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  512. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  513. <intercept-message pattern="/**" access="denyAll"/>
  514. </websocket-message-broker>
  515. ----
  516. ====
  517. changes to:
  518. ====
  519. .Java
  520. [source,java,role="primary"]
  521. ----
  522. @Bean
  523. AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
  524. messages
  525. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  526. .simpDestMatchers("/user/queue/errors").permitAll()
  527. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  528. .anyMessage().denyAll();
  529. return messages.build();
  530. }
  531. ----
  532. .Kotlin
  533. [source,kotlin,role="secondary"]
  534. ----
  535. @Bean
  536. fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
  537. messages
  538. .simpTypeMatchers(CONNECT, DISCONNECT, UNSUBSCRIBE).permitAll()
  539. .simpDestMatchers("/user/queue/errors").permitAll()
  540. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  541. .anyMessage().denyAll()
  542. return messages.build()
  543. }
  544. ----
  545. .Xml
  546. [source,xml,role="secondary"]
  547. ----
  548. <websocket-message-broker use-authorization-manager="true">
  549. <intercept-message type="CONNECT" access="permitAll"/>
  550. <intercept-message type="DISCONNECT" access="permitAll"/>
  551. <intercept-message type="UNSUBSCRIBE" access="permitAll"/>
  552. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  553. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  554. <intercept-message pattern="/**" access="denyAll"/>
  555. </websocket-message-broker>
  556. ----
  557. ====
  558. === Stop Implementing `AbstractSecurityWebSocketMessageBrokerConfigurer`
  559. If you are using Java configuration, you can now simply extend `WebSocketMessageBrokerConfigurer`.
  560. For example, if your class that extends `AbstractSecurityWebSocketMessageBrokerConfigurer` is called `WebSocketSecurityConfig`, then:
  561. ====
  562. .Java
  563. [source,java,role="primary"]
  564. ----
  565. @EnableWebSocketSecurity
  566. @Configuration
  567. public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
  568. // ...
  569. }
  570. ----
  571. .Kotlin
  572. [source,kotlin,role="secondary"]
  573. ----
  574. @EnableWebSocketSecurity
  575. @Configuration
  576. class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer() {
  577. // ...
  578. }
  579. ----
  580. ====
  581. changes to:
  582. ====
  583. .Java
  584. [source,java,role="primary"]
  585. ----
  586. @EnableWebSocketSecurity
  587. @Configuration
  588. public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
  589. // ...
  590. }
  591. ----
  592. .Kotlin
  593. [source,kotlin,role="secondary"]
  594. ----
  595. @EnableWebSocketSecurity
  596. @Configuration
  597. class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer {
  598. // ...
  599. }
  600. ----
  601. ====
  602. [[servlet-authorizationmanager-messages-opt-out]]
  603. === Opt-out Steps
  604. In case you had trouble, take a look at these scenarios for optimal opt out behavior:
  605. ==== I cannot declare an authorization rule for all requests
  606. 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:
  607. ====
  608. .Java
  609. [source,java,role="primary"]
  610. ----
  611. @Bean
  612. AuthorizationManager<Message<?>> messageSecurity(MessageMatcherDelegatingAuthorizationManager.Builder messages) {
  613. messages
  614. .simpDestMatchers("/user/queue/errors").permitAll()
  615. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  616. // ...
  617. .anyMessage().permitAll();
  618. return messages.build();
  619. }
  620. ----
  621. .Kotlin
  622. [source,kotlin,role="secondary"]
  623. ----
  624. @Bean
  625. fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.Builder): AuthorizationManager<Message<?>> {
  626. messages
  627. .simpDestMatchers("/user/queue/errors").permitAll()
  628. .simpDestMatchers("/admin/**").hasRole("ADMIN")
  629. // ...
  630. .anyMessage().permitAll();
  631. return messages.build()
  632. }
  633. ----
  634. .Xml
  635. [source,xml,role="secondary"]
  636. ----
  637. <websocket-message-broker use-authorization-manager="true">
  638. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  639. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  640. <!-- ... -->
  641. <intercept-message pattern="/**" access="permitAll"/>
  642. </websocket-message-broker>
  643. ----
  644. ====
  645. ==== I cannot get CSRF working, need some other `AbstractSecurityWebSocketMessageBrokerConfigurer` feature, or am having trouble with `AuthorizationManager`
  646. In the case of Java, you may continue using `AbstractMessageSecurityWebSocketMessageBrokerConfigurer`.
  647. Even though it is deprecated, it will not be removed in 6.0.
  648. In the case of XML, you can opt out of `AuthorizationManager` by setting `use-authorization-manager="false"`:
  649. ====
  650. .Xml
  651. [source,xml,role="secondary"]
  652. ----
  653. <websocket-message-broker>
  654. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  655. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  656. </websocket-message-broker>
  657. ----
  658. ====
  659. to:
  660. ====
  661. .Xml
  662. [source,xml,role="secondary"]
  663. ----
  664. <websocket-message-broker use-authorization-manager="false">
  665. <intercept-message pattern="/user/queue/errors" access="permitAll"/>
  666. <intercept-message pattern="/admin/**" access="hasRole('ADMIN')"/>
  667. </websocket-message-broker>
  668. ----
  669. ====
  670. == Use `AuthorizationManager` for Request Security
  671. 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].
  672. 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.
  673. === Ensure that all requests have defined authorization rules
  674. In Spring Security 5.8 and earlier, requests with no authorization rule are permitted by default.
  675. It is a stronger security position to deny by default, thus requiring that authorization rules be clearly defined for every endpoint.
  676. As such, in 6.0, Spring Security by default denies any request that is missing an authorization rule.
  677. 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.
  678. 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.
  679. [NOTE]
  680. ====
  681. You may already have an `anyRequest` rule defined that you are happy with in which case this step can be skipped.
  682. ====
  683. Adding `denyAll` to the end looks like changing:
  684. ====
  685. .Java
  686. [source,java,role="primary"]
  687. ----
  688. http
  689. .authorizeRequests((authorize) -> authorize
  690. .filterSecurityInterceptorOncePerRequest(true)
  691. .mvcMatchers("/app/**").hasRole("APP")
  692. // ...
  693. )
  694. // ...
  695. ----
  696. .Kotlin
  697. [source,kotlin,role="secondary"]
  698. ----
  699. http {
  700. authorizeRequests {
  701. filterSecurityInterceptorOncePerRequest = true
  702. authorize("/app/**", hasRole("APP"))
  703. // ...
  704. }
  705. }
  706. ----
  707. .Xml
  708. [source,xml,role="secondary"]
  709. ----
  710. <http once-per-request="true">
  711. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  712. <!-- ... -->
  713. </http>
  714. ----
  715. ====
  716. to:
  717. ====
  718. .Java
  719. [source,java,role="primary"]
  720. ----
  721. http
  722. .authorizeRequests((authorize) -> authorize
  723. .filterSecurityInterceptorOncePerRequest(true)
  724. .mvcMatchers("/app/**").hasRole("APP")
  725. // ...
  726. .anyRequest().denyAll()
  727. )
  728. // ...
  729. ----
  730. .Kotlin
  731. [source,kotlin,role="secondary"]
  732. ----
  733. http {
  734. authorizeRequests {
  735. filterSecurityInterceptorOncePerRequest = true
  736. authorize("/app/**", hasRole("APP"))
  737. // ...
  738. authorize(anyRequest, denyAll)
  739. }
  740. }
  741. ----
  742. .Xml
  743. [source,xml,role="secondary"]
  744. ----
  745. <http once-per-request="true">
  746. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  747. <!-- ... -->
  748. <intercept-url pattern="/**" access="denyAll"/>
  749. </http>
  750. ----
  751. ====
  752. If you have already migrated to `authorizeHttpRequests`, the recommended change is the same.
  753. === Switch to `AuthorizationManager`
  754. 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.
  755. Change:
  756. ====
  757. .Java
  758. [source,java,role="primary"]
  759. ----
  760. http
  761. .authorizeRequests((authorize) -> authorize
  762. .filterSecurityInterceptorOncePerRequest(true)
  763. .mvcMatchers("/app/**").hasRole("APP")
  764. // ...
  765. .anyRequest().denyAll()
  766. )
  767. // ...
  768. ----
  769. .Kotlin
  770. [source,kotlin,role="secondary"]
  771. ----
  772. http {
  773. authorizeRequests {
  774. filterSecurityInterceptorOncePerRequest = true
  775. authorize("/app/**", hasRole("APP"))
  776. // ...
  777. authorize(anyRequest, denyAll)
  778. }
  779. }
  780. ----
  781. .Xml
  782. [source,xml,role="secondary"]
  783. ----
  784. <http once-per-request="true">
  785. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  786. <!-- ... -->
  787. <intercept-url pattern="/**" access="denyAll"/>
  788. </http>
  789. ----
  790. ====
  791. to:
  792. ====
  793. .Java
  794. [source,java,role="primary"]
  795. ----
  796. http
  797. .authorizeHttpRequests((authorize) -> authorize
  798. .shouldFilterAllDispatcherTypes(false)
  799. .mvcMatchers("/app/**").hasRole("APP")
  800. // ...
  801. .anyRequest().denyAll()
  802. )
  803. // ...
  804. ----
  805. .Kotlin
  806. [source,kotlin,role="secondary"]
  807. ----
  808. http {
  809. authorizeHttpRequests {
  810. shouldFilterAllDispatcherTypes = false
  811. authorize("/app/**", hasRole("APP"))
  812. // ...
  813. authorize(anyRequest, denyAll)
  814. }
  815. }
  816. ----
  817. .Xml
  818. [source,xml,role="secondary"]
  819. ----
  820. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  821. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  822. <!-- ... -->
  823. <intercept-url pattern="/**" access="denyAll"/>
  824. </http>
  825. ----
  826. ====
  827. === Migrate SpEL expressions to `AuthorizationManager`
  828. For authorization rules, Java tends to be easier to test and maintain than SpEL.
  829. As such, `authorizeHttpRequests` does not have a method for declaring a `String` SpEL.
  830. Instead, you can implement your own `AuthorizationManager` implementation or use `WebExpressionAuthorizationManager`.
  831. For completeness, both options will be demonstrated.
  832. First, if you have the following SpEL:
  833. ====
  834. .Java
  835. [source,java,role="primary"]
  836. ----
  837. http
  838. .authorizeRequests((authorize) -> authorize
  839. .filterSecurityInterceptorOncePerRequest(true)
  840. .mvcMatchers("/complicated/**").access("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
  841. // ...
  842. .anyRequest().denyAll()
  843. )
  844. // ...
  845. ----
  846. .Kotlin
  847. [source,kotlin,role="secondary"]
  848. ----
  849. http {
  850. authorizeRequests {
  851. filterSecurityInterceptorOncePerRequest = true
  852. authorize("/complicated/**", access("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
  853. // ...
  854. authorize(anyRequest, denyAll)
  855. }
  856. }
  857. ----
  858. ====
  859. Then you can compose your own `AuthorizationManager` with Spring Security authorization primitives like so:
  860. ====
  861. .Java
  862. [source,java,role="primary"]
  863. ----
  864. http
  865. .authorizeHttpRequests((authorize) -> authorize
  866. .shouldFilterAllDispatcherTypes(false)
  867. .mvcMatchers("/complicated/**").access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
  868. // ...
  869. .anyRequest().denyAll()
  870. )
  871. // ...
  872. ----
  873. .Kotlin
  874. [source,kotlin,role="secondary"]
  875. ----
  876. http {
  877. authorizeHttpRequests {
  878. shouldFilterAllDispatcherTypes = false
  879. authorize("/complicated/**", access(anyOf(hasRole("ADMIN"), hasAuthority("SCOPE_read"))
  880. // ...
  881. authorize(anyRequest, denyAll)
  882. }
  883. }
  884. ----
  885. ====
  886. Or you can use `WebExpressionAuthorizationManager` in the following way:
  887. ====
  888. .Java
  889. [source,java,role="primary"]
  890. ----
  891. http
  892. .authorizeRequests((authorize) -> authorize
  893. .filterSecurityInterceptorOncePerRequest(true)
  894. .mvcMatchers("/complicated/**").access(
  895. new WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')")
  896. )
  897. // ...
  898. .anyRequest().denyAll()
  899. )
  900. // ...
  901. ----
  902. .Kotlin
  903. [source,kotlin,role="secondary"]
  904. ----
  905. http {
  906. authorizeRequests {
  907. filterSecurityInterceptorOncePerRequest = true
  908. authorize("/complicated/**", access(
  909. WebExpressionAuthorizationManager("hasRole('ADMIN') || hasAuthority('SCOPE_read')"))
  910. )
  911. // ...
  912. authorize(anyRequest, denyAll)
  913. }
  914. }
  915. ----
  916. ====
  917. [[switch-filter-all-dispatcher-types]]
  918. === Switch to filter all dispatcher types
  919. Spring Security 5.8 and earlier only xref:servlet/authorization/architecture.adoc[perform authorization] once per request.
  920. This means that dispatcher types like `FORWARD` and `INCLUDE` that run after `REQUEST` are not secured by default.
  921. It's recommended that Spring Security secure all dispatch types.
  922. As such, in 6.0, Spring Security changes this default.
  923. So, finally, change your authorization rules to filter all dispatcher types.
  924. To do this, you should change:
  925. ====
  926. .Java
  927. [source,java,role="primary"]
  928. ----
  929. http
  930. .authorizeHttpRequests((authorize) -> authorize
  931. .shouldFilterAllDispatcherTypes(false)
  932. .mvcMatchers("/app/**").hasRole("APP")
  933. // ...
  934. .anyRequest().denyAll()
  935. )
  936. // ...
  937. ----
  938. .Kotlin
  939. [source,kotlin,role="secondary"]
  940. ----
  941. http {
  942. authorizeHttpRequests {
  943. shouldFilterAllDispatcherTypes = false
  944. authorize("/app/**", hasRole("APP"))
  945. // ...
  946. authorize(anyRequest, denyAll)
  947. }
  948. }
  949. ----
  950. .Xml
  951. [source,xml,role="secondary"]
  952. ----
  953. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  954. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  955. <!-- ... -->
  956. <intercept-url pattern="/**" access="denyAll"/>
  957. </http>
  958. ----
  959. ====
  960. to:
  961. ====
  962. .Java
  963. [source,java,role="primary"]
  964. ----
  965. http
  966. .authorizeHttpRequests((authorize) -> authorize
  967. .shouldFilterAllDispatcherTypes(true)
  968. .mvcMatchers("/app/**").hasRole("APP")
  969. // ...
  970. .anyRequest().denyAll()
  971. )
  972. // ...
  973. ----
  974. .Kotlin
  975. [source,kotlin,role="secondary"]
  976. ----
  977. http {
  978. authorizeHttpRequests {
  979. shouldFilterAllDispatcherTypes = true
  980. authorize("/app/**", hasRole("APP"))
  981. // ...
  982. authorize(anyRequest, denyAll)
  983. }
  984. }
  985. ----
  986. .Xml
  987. [source,xml,role="secondary"]
  988. ----
  989. <http filter-all-dispatcher-types="true" use-authorization-manager="true">
  990. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  991. <!-- ... -->
  992. <intercept-url pattern="/**" access="denyAll"/>
  993. </http>
  994. ----
  995. ====
  996. And, the `FilterChainProxy` should be registered for all dispatcher types as well.
  997. If you are using Spring Boot, https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.security.spring.security.filter.dispatcher-types[you have to change the `spring.security.filter.dispatcher-types` property] to include all dispatcher types:
  998. ====
  999. .application.properties
  1000. [source,properties,role="primary"]
  1001. ----
  1002. spring.security.filter.dispatcher-types=request,async,error,forward,include
  1003. ----
  1004. ====
  1005. If you are xref:servlet/configuration/java.adoc#_abstractsecuritywebapplicationinitializer[using the `AbstractSecurityWebApplicationInitializer`] you should override the `getSecurityDispatcherTypes` method and return all dispatcher types:
  1006. ====
  1007. .Java
  1008. [source,java,role="primary"]
  1009. ----
  1010. import org.springframework.security.web.context.*;
  1011. public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
  1012. @Override
  1013. protected EnumSet<DispatcherType> getSecurityDispatcherTypes() {
  1014. return EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC,
  1015. DispatcherType.FORWARD, DispatcherType.INCLUDE);
  1016. }
  1017. }
  1018. ----
  1019. ====
  1020. ==== Permit `FORWARD` when using Spring MVC
  1021. If you are using {spring-framework-reference-url}/web.html#mvc-viewresolver[Spring MVC to resolve view names], you will need to permit `FORWARD` requests.
  1022. This is because when Spring MVC detects a mapping between view name and the actual views, it will perform a forward to the view.
  1023. As we saw on the <<switch-filter-all-dispatcher-types,previous section>>, Spring Security 6.0 will apply authorization to `FORWARD` requests by default.
  1024. Consider the following common configuration:
  1025. ====
  1026. .Java
  1027. [source,java,role="primary"]
  1028. ----
  1029. @Bean
  1030. public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  1031. http
  1032. .authorizeHttpRequests((authorize) -> authorize
  1033. .shouldFilterAllDispatcherTypes(true)
  1034. .requestMatchers("/").authenticated()
  1035. .anyRequest().denyAll()
  1036. )
  1037. .formLogin((form) -> form
  1038. .loginPage("/login")
  1039. .permitAll()
  1040. ));
  1041. return http.build();
  1042. }
  1043. ----
  1044. ====
  1045. and one of the following equivalents MVC view mapping configurations:
  1046. ====
  1047. .Java
  1048. [source,java,role="primary"]
  1049. ----
  1050. @Controller
  1051. public class MyController {
  1052. @GetMapping("/login")
  1053. public String login() {
  1054. return "login";
  1055. }
  1056. }
  1057. ----
  1058. ====
  1059. ====
  1060. .Java
  1061. [source,java,role="primary"]
  1062. ----
  1063. @Configuration
  1064. public class MyWebMvcConfigurer implements WebMvcConfigurer {
  1065. @Override
  1066. public void addViewControllers(ViewControllerRegistry registry) {
  1067. registry.addViewController("/login").setViewName("login");
  1068. }
  1069. }
  1070. ----
  1071. ====
  1072. With either configuration, when there is a request to `/login`, Spring MVC will perform a *forward* to the view `login`, which, with the default configuration, is under `src/main/resources/templates/login.html` path.
  1073. The security configuration permits requests to `/login` but every other request will be denied, including the `FORWARD` request to the view under `/templates/login.html`.
  1074. To fix this, you should configure Spring Security to permit `FORWARD` requests:
  1075. ====
  1076. .Java
  1077. [source,java,role="primary"]
  1078. ----
  1079. http
  1080. .authorizeHttpRequests((authorize) -> authorize
  1081. .shouldFilterAllDispatcherTypes(true)
  1082. .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
  1083. .anyRequest().denyAll()
  1084. )
  1085. // ...
  1086. ----
  1087. .Kotlin
  1088. [source,kotlin,role="secondary"]
  1089. ----
  1090. http {
  1091. authorizeHttpRequests {
  1092. shouldFilterAllDispatcherTypes = true
  1093. authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll)
  1094. authorize(anyRequest, denyAll)
  1095. }
  1096. }
  1097. ----
  1098. .Xml
  1099. [source,xml,role="secondary"]
  1100. ----
  1101. <http filter-all-dispatcher-types="true" use-authorization-manager="true">
  1102. <intercept-url request-matcher-ref="forwardRequestMatcher" access="permitAll()" />
  1103. <!-- ... -->
  1104. <intercept-url pattern="/**" access="denyAll"/>
  1105. </http>
  1106. <bean name="forwardRequestMatcher" class="org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher">
  1107. <constructor-arg value="FORWARD"/>
  1108. </bean>
  1109. ----
  1110. ====
  1111. === Replace any custom filter-security ``AccessDecisionManager``s
  1112. 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.
  1113. The preparation strategy will depend on your reason for each arrangement.
  1114. Read on to find the best match for your situation.
  1115. ==== I use `UnanimousBased`
  1116. 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:
  1117. ====
  1118. .Java
  1119. [source,java,role="primary"]
  1120. ----
  1121. @Bean
  1122. AuthorizationManager<RequestAuthorizationContext> requestAuthorization() {
  1123. PolicyAuthorizationManager policy = ...;
  1124. LocalAuthorizationManager local = ...;
  1125. return AuthorizationManagers.allOf(policy, local);
  1126. }
  1127. ----
  1128. .Kotlin
  1129. [source,kotlin,role="secondary"]
  1130. ----
  1131. @Bean
  1132. fun requestAuthorization(): AuthorizationManager<RequestAuthorizationContext> {
  1133. val policy: PolicyAuthorizationManager = ...
  1134. val local: LocalAuthorizationManager = ...
  1135. return AuthorizationManagers.allOf(policy, local)
  1136. }
  1137. ----
  1138. .Xml
  1139. [source,xml,role="secondary"]
  1140. ----
  1141. <bean id="requestAuthorization" class="org.springframework.security.authorization.AuthorizationManagers"
  1142. factory-method="allOf">
  1143. <constructor-arg>
  1144. <util:list>
  1145. <bean class="my.PolicyAuthorizationManager"/>
  1146. <bean class="my.LocalAuthorizationManager"/>
  1147. </util:list>
  1148. </constructor-arg>
  1149. </bean>
  1150. ----
  1151. ====
  1152. then, wire it into the DSL like so:
  1153. ====
  1154. .Java
  1155. [source,java,role="primary"]
  1156. ----
  1157. http
  1158. .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
  1159. // ...
  1160. ----
  1161. .Kotlin
  1162. [source,kotlin,role="secondary"]
  1163. ----
  1164. http {
  1165. authorizeHttpRequests {
  1166. authorize(anyRequest, requestAuthorization)
  1167. }
  1168. // ...
  1169. }
  1170. ----
  1171. .Xml
  1172. [source,xml,role="secondary"]
  1173. ----
  1174. <http authorization-manager-ref="requestAuthorization"/>
  1175. ----
  1176. ====
  1177. [NOTE]
  1178. ====
  1179. `authorizeHttpRequests` is designed so that you can apply a custom `AuthorizationManager` to any url pattern.
  1180. See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[the reference] for more details.
  1181. ====
  1182. ==== I use `AffirmativeBased`
  1183. 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:
  1184. ====
  1185. .Java
  1186. [source,java,role="primary"]
  1187. ----
  1188. @Bean
  1189. AuthorizationManager<RequestAuthorizationContext> requestAuthorization() {
  1190. PolicyAuthorizationManager policy = ...;
  1191. LocalAuthorizationManager local = ...;
  1192. return AuthorizationManagers.anyOf(policy, local);
  1193. }
  1194. ----
  1195. .Kotlin
  1196. [source,kotlin,role="secondary"]
  1197. ----
  1198. @Bean
  1199. fun requestAuthorization(): AuthorizationManager<RequestAuthorizationContext> {
  1200. val policy: PolicyAuthorizationManager = ...
  1201. val local: LocalAuthorizationManager = ...
  1202. return AuthorizationManagers.anyOf(policy, local)
  1203. }
  1204. ----
  1205. .Xml
  1206. [source,xml,role="secondary"]
  1207. ----
  1208. <bean id="requestAuthorization" class="org.springframework.security.authorization.AuthorizationManagers"
  1209. factory-method="anyOf">
  1210. <constructor-arg>
  1211. <util:list>
  1212. <bean class="my.PolicyAuthorizationManager"/>
  1213. <bean class="my.LocalAuthorizationManager"/>
  1214. </util:list>
  1215. </constructor-arg>
  1216. </bean>
  1217. ----
  1218. ====
  1219. then, wire it into the DSL like so:
  1220. ====
  1221. .Java
  1222. [source,java,role="primary"]
  1223. ----
  1224. http
  1225. .authorizeHttpRequests((authorize) -> authorize.anyRequest().access(requestAuthorization))
  1226. // ...
  1227. ----
  1228. .Kotlin
  1229. [source,kotlin,role="secondary"]
  1230. ----
  1231. http {
  1232. authorizeHttpRequests {
  1233. authorize(anyRequest, requestAuthorization)
  1234. }
  1235. // ...
  1236. }
  1237. ----
  1238. .Xml
  1239. [source,xml,role="secondary"]
  1240. ----
  1241. <http authorization-manager-ref="requestAuthorization"/>
  1242. ----
  1243. ====
  1244. [NOTE]
  1245. ====
  1246. `authorizeHttpRequests` is designed so that you can apply a custom `AuthorizationManager` to any url pattern.
  1247. See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[the reference] for more details.
  1248. ====
  1249. ==== I use `ConsensusBased`
  1250. There is no framework-provided equivalent for {security-api-url}org/springframework/security/access/vote/ConsensusBased.html[`ConsensusBased`].
  1251. 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.
  1252. 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`].
  1253. ==== I use a custom `AccessDecisionVoter`
  1254. You should either change the class to implement {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`] or create an adapter.
  1255. Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution.
  1256. 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:
  1257. ====
  1258. .Java
  1259. [source,java,role="primary"]
  1260. ----
  1261. public final class AnyRequestAuthenticatedAuthorizationManagerAdapter implements AuthorizationManager<RequestAuthorizationContext> {
  1262. private final SecurityMetadataSource metadata;
  1263. private final AccessDecisionVoter voter;
  1264. public PreAuthorizeAuthorizationManagerAdapter(SecurityExpressionHandler expressionHandler) {
  1265. Map<RequestMatcher, List<ConfigAttribute>> requestMap = Collections.singletonMap(
  1266. AnyRequestMatcher.INSTANCE, Collections.singletonList(new SecurityConfig("authenticated")));
  1267. this.metadata = new DefaultFilterInvocationSecurityMetadataSource(requestMap);
  1268. WebExpressionVoter voter = new WebExpressionVoter();
  1269. voter.setExpressionHandler(expressionHandler);
  1270. this.voter = voter;
  1271. }
  1272. public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
  1273. List<ConfigAttribute> attributes = this.metadata.getAttributes(context);
  1274. int decision = this.voter.vote(authentication.get(), invocation, attributes);
  1275. if (decision == ACCESS_GRANTED) {
  1276. return new AuthorizationDecision(true);
  1277. }
  1278. if (decision == ACCESS_DENIED) {
  1279. return new AuthorizationDecision(false);
  1280. }
  1281. return null; // abstain
  1282. }
  1283. }
  1284. ----
  1285. ====
  1286. 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`].
  1287. [[servlet-authorizationmanager-requests-opt-out]]
  1288. === Opt-out Steps
  1289. In case you had trouble, take a look at these scenarios for optimal opt out behavior:
  1290. ==== I cannot secure all dispatcher types
  1291. If you cannot secure all dispatcher types, first try and declare which dispatcher types should not require authorization like so:
  1292. ====
  1293. .Java
  1294. [source,java,role="primary"]
  1295. ----
  1296. http
  1297. .authorizeHttpRequests((authorize) -> authorize
  1298. .shouldFilterAllDispatcherTypes(true)
  1299. .dispatcherTypeMatchers(FORWARD, INCLUDE).permitAll()
  1300. .mvcMatchers("/app/**").hasRole("APP")
  1301. // ...
  1302. .anyRequest().denyAll()
  1303. )
  1304. // ...
  1305. ----
  1306. .Kotlin
  1307. [source,kotlin,role="secondary"]
  1308. ----
  1309. http {
  1310. authorizeHttpRequests {
  1311. shouldFilterAllDispatcherTypes = true
  1312. authorize(DispatcherTypeRequestMatcher(FORWARD, INCLUDE), permitAll)
  1313. authorize("/app/**", hasRole("APP"))
  1314. // ...
  1315. authorize(anyRequest, denyAll)
  1316. }
  1317. }
  1318. ----
  1319. .Xml
  1320. [source,xml,role="secondary"]
  1321. ----
  1322. <http filter-all-dispatcher-types="true" use-authorization-manager="true">
  1323. <intercept-url request-matcher-ref="dispatchers"/>
  1324. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1325. <!-- ... -->
  1326. <intercept-url pattern="/**" access="denyAll"/>
  1327. </http>
  1328. <bean id="dispatchers" class="org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher">
  1329. <constructor-arg>
  1330. <util:list value-type="javax.servlet.DispatcherType">
  1331. <value>FORWARD</value>
  1332. <value>INCLUDE</value>
  1333. </util:list>
  1334. </constructor-arg>
  1335. </bean>
  1336. ----
  1337. ====
  1338. 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`:
  1339. ====
  1340. .Java
  1341. [source,java,role="primary"]
  1342. ----
  1343. http
  1344. .authorizeHttpRequests((authorize) -> authorize
  1345. .filterAllDispatcherTypes(false)
  1346. .mvcMatchers("/app/**").hasRole("APP")
  1347. // ...
  1348. )
  1349. // ...
  1350. ----
  1351. .Kotlin
  1352. [source,kotlin,role="secondary"]
  1353. ----
  1354. http {
  1355. authorizeHttpRequests {
  1356. filterAllDispatcherTypes = false
  1357. authorize("/messages/**", hasRole("APP"))
  1358. // ...
  1359. }
  1360. }
  1361. ----
  1362. .Xml
  1363. [source,xml,role="secondary"]
  1364. ----
  1365. <http filter-all-dispatcher-types="false" use-authorization-manager="true">
  1366. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1367. <!-- ... -->
  1368. </http>
  1369. ----
  1370. ====
  1371. or, if you are still using `authorizeRequests` or `use-authorization-manager="false"`, set `oncePerRequest` to `true`:
  1372. ====
  1373. .Java
  1374. [source,java,role="primary"]
  1375. ----
  1376. http
  1377. .authorizeRequests((authorize) -> authorize
  1378. .filterSecurityInterceptorOncePerRequest(true)
  1379. .mvcMatchers("/app/**").hasRole("APP")
  1380. // ...
  1381. )
  1382. // ...
  1383. ----
  1384. .Kotlin
  1385. [source,kotlin,role="secondary"]
  1386. ----
  1387. http {
  1388. authorizeRequests {
  1389. filterSecurityInterceptorOncePerRequest = true
  1390. authorize("/messages/**", hasRole("APP"))
  1391. // ...
  1392. }
  1393. }
  1394. ----
  1395. .Xml
  1396. [source,xml,role="secondary"]
  1397. ----
  1398. <http once-per-request="true" use-authorization-manager="false">
  1399. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1400. <!-- ... -->
  1401. </http>
  1402. ----
  1403. ====
  1404. ==== I cannot declare an authorization rule for all requests
  1405. 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:
  1406. ====
  1407. .Java
  1408. [source,java,role="primary"]
  1409. ----
  1410. http
  1411. .authorizeHttpReqeusts((authorize) -> authorize
  1412. .mvcMatchers("/app/*").hasRole("APP")
  1413. // ...
  1414. .anyRequest().permitAll()
  1415. )
  1416. ----
  1417. .Kotlin
  1418. [source,kotlin,role="secondary"]
  1419. ----
  1420. http {
  1421. authorizeHttpRequests {
  1422. authorize("/app*", hasRole("APP"))
  1423. // ...
  1424. authorize(anyRequest, permitAll)
  1425. }
  1426. }
  1427. ----
  1428. .Xml
  1429. [source,xml,role="secondary"]
  1430. ----
  1431. <http>
  1432. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1433. <!-- ... -->
  1434. <intercept-url pattern="/**" access="permitAll"/>
  1435. </http>
  1436. ----
  1437. ====
  1438. ==== I cannot migrate my SpEL or my `AccessDecisionManager`
  1439. 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.
  1440. 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.
  1441. Second, if you still need your custom `access-decision-manager-ref` or have some other reason to opt out of `AuthorizationManager`, do:
  1442. ====
  1443. .Xml
  1444. [source,xml,role="secondary"]
  1445. ----
  1446. <http use-authorization-manager="false">
  1447. <intercept-url pattern="/app/*" access="hasRole('APP')"/>
  1448. <!-- ... -->
  1449. </http>
  1450. ----
  1451. ====