method-security.adoc 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637
  1. [[jc-method]]
  2. = Method Security
  3. :figures: servlet/authorization
  4. In addition to xref:servlet/authorization/authorize-http-requests.adoc[modeling authorization at the request level], Spring Security also supports modeling at the method level.
  5. [[activate-method-security]]
  6. You can activate it in your application by annotating any `@Configuration` class with `@EnableMethodSecurity` or adding `<method-security>` to any XML configuration file, like so:
  7. ====
  8. .Java
  9. [source,java,role="primary"]
  10. ----
  11. @EnableMethodSecurity
  12. ----
  13. .Kotlin
  14. [source,kotlin,role="secondary"]
  15. ----
  16. @EnableMethodSecurity
  17. ----
  18. .Xml
  19. [source,xml,role="secondary"]
  20. ----
  21. <sec:method-security/>
  22. ----
  23. ====
  24. Then, you are immediately able to annotate any Spring-managed class or method with <<use-preauthorize, `@PreAuthorize`>>, <<use-postauthorize,`@PostAuthorize`>>, <<use-prefilter,`@PreFilter`>>, and <<use-postfilter,`@PostFilter`>> to authorize method invocations, including the input parameters and return values.
  25. [NOTE]
  26. {spring-boot-reference-url}using.html#using.build-systems.starters[Spring Boot Starter Security] does not activate method-level authorization by default.
  27. Method Security supports many other use cases as well including <<use-aspectj, AspectJ support>>, <<use-programmatic-authorization,custom annotations>>, and several configuration points.
  28. Consider learning about the following use cases:
  29. * <<migration-enableglobalmethodsecurity, Migrating from `@EnableGlobalMethodSecurity`>>
  30. * Understanding <<method-security-architecture,how method security works>> and reasons to use it
  31. * Comparing <<request-vs-method,request-level and method-level authorization>>
  32. * Authorizing methods with <<use-preauthorize,`@PreAuthorize`>> and <<use-postauthorize,`@PostAuthorize`>>
  33. * Filtering methods with <<use-prefilter,`@PreFilter`>> and <<use-postfilter,`@PostFilter`>>
  34. * Authorizing methods with <<use-jsr250,JSR-250 annotations>>
  35. * Authorizing methods with <<use-aspectj,AspectJ expressions>>
  36. * Integrating with <<weave-aspectj,AspectJ byte-code weaving>>
  37. * Coordinating with <<changing-the-order,@Transactional and other AOP-based annotations>>
  38. * Customizing <<customizing-expression-handling,SpEL expression handling>>
  39. * Integrating with <<custom-authorization-managers,custom authorization systems>>
  40. [[method-security-architecture]]
  41. == How Method Security Works
  42. Spring Security's method authorization support is handy for:
  43. * Extracting fine-grained authorization logic; for example, when the method parameters and return values contribute to the authorization decision.
  44. * Enforcing security at the service layer
  45. * Stylistically favoring annotation-based over `HttpSecurity`-based configuration
  46. And since Method Security is built using {spring-framework-reference-url}core.html#aop-api[Spring AOP], you have access to all its expressive power to override Spring Security's defaults as needed.
  47. As already mentioned, you begin by adding `@EnableMethodSecurity` to a `@Configuration` class or `<sec:method-security/>` in a Spring XML configuration file.
  48. [[use-method-security]]
  49. [NOTE]
  50. ====
  51. This annotation and XML element supercede `@EnableGlobalMethodSecurity` and `<sec:global-method-security/>`, respectively.
  52. They offer the following improvements:
  53. 1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters.
  54. This simplifies reuse and customization.
  55. 2. Favors direct bean-based configuration, instead of requiring extending `GlobalMethodSecurityConfiguration` to customize beans
  56. 3. Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize
  57. 4. Checks for conflicting annotations to ensure an unambiguous security configuration
  58. 5. Complies with JSR-250
  59. 6. Enables `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` by default
  60. If you are using `@EnableGlobalMethodSecurity` or `<global-method-security/>`, these are now deprecated, and you are encouraged to migrate.
  61. ====
  62. Method authorization is a combination of before- and after-method authorization.
  63. Consider a service bean that is annotated in the following way:
  64. ====
  65. .Java
  66. [source,java,role="primary"]
  67. ----
  68. @Service
  69. public class MyCustomerService {
  70. @PreAuthorize("hasAuthority('permission:read')")
  71. @PostAuthorize("returnObject.owner == authentication.name")
  72. public Customer readCustomer(String id) { ... }
  73. }
  74. ----
  75. .Kotlin
  76. [source,kotlin,role="secondary"]
  77. ----
  78. @Service
  79. open class MyCustomerService {
  80. @PreAuthorize("hasAuthority('permission:read')")
  81. @PostAuthorize("returnObject.owner == authentication.name")
  82. fun readCustomer(val id: String): Customer { ... }
  83. }
  84. ----
  85. ====
  86. A given invocation to `MyCustomerService#readCustomer` may look something like this when Method Security <<activate-method-security,is activated>>:
  87. image::{figures}/methodsecurity.png[]
  88. 1. Spring AOP invokes its proxy method for `readCustomer`. Among the proxy's other advisors, it invokes an {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor/html[`AuthorizationManagerBeforeMethodInterceptor`] that matches <<annotation-method-pointcuts,the `@PreAuthorize` pointcut>>
  89. 2. The interceptor invokes {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager#check`]
  90. 3. The authorization manager uses a `MethodSecurityExpressionHandler` to parse the annotation's <<authorization-expressions,SpEL expression>> and constructs a corresponding `EvaluationContext` from a `MethodSecurityExpressionRoot` containing xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[a `Supplier<Authentication>`] and `MethodInvocation`.
  91. 4. The interceptor uses this context to evaluate the expression; specifically, it reads xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] from the `Supplier` and checks whether it has `permission:read` in its collection of xref:servlet/authorization/architecture.adoc#authz-authorities[authorities]
  92. 5. If the evaluation passes, then Spring AOP proceeds to invoke the method.
  93. 6. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an {security-api-url}org/springframework/security/access/AccessDeniedException.html[`AccessDeniedException`] which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response
  94. 7. After the method returns, Spring AOP invokes an {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.html[`AuthorizationManagerAfterMethodInterceptor`] that matches <<annotation-method-pointcuts,the `@PostAuthorize` pointcut>>, operating the same as above, but with {security-api-url}org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.html[`PostAuthorizeAuthorizationManager`]
  95. 8. If the evaluation passes (in this case, the return value belongs to the logged-in user), processing continues normally
  96. 9. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an {security-api-url}org/springframework/security/access/AccessDeniedException.html[`AccessDeniedException`], which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response
  97. [NOTE]
  98. If the method is not being called in the context of an HTTP request, you will likely need to handle the `AccessDeniedException` yourself
  99. [[unanimous-based-authorization-decisions]]
  100. === Multiple Annotations Are Computed In Series
  101. As demonstrated above, if a method invocation involves multiple <<authorizing-with-annotations,Method Security annotations>>, each of those is processed one at a time.
  102. This means that they can collectively be thought of as being "anded" together.
  103. In other words, for an invocation to be authorized, all annotation inspections need to pass authorization.
  104. [[repeated-annotations]]
  105. === Repeated Annotations Are Not Supported
  106. That said, it is not supported to repeat the same annotation on the same method.
  107. For example, you cannot please `@PreAuthorize` twice on the same method.
  108. Instead, use SpEL's boolean support or its support for delegating to a separate bean.
  109. [[annotation-method-pointcuts]]
  110. === Each Annotation Has Its Own Pointcut
  111. Each annotation has its own pointcut instance that looks for that annotation or its <<meta-annotations,meta-annotation>> counterparts across the entire object hierarchy, starting at <<class-or-interface-annotations,the method and its enclosing class>>.
  112. You can see the specifics of this in {security-api-url}org/springframework/security/authorization/method/AuthorizationMethodPointcuts.html[`AuthorizationMethodPointcuts`].
  113. [[annotation-method-interceptors]]
  114. === Each Annotation Has Its Own Method Interceptor
  115. Each annotation has its own dedicated method interceptor.
  116. The reason for this is to make things more composable.
  117. For example, if needed, you can disable the Spring Security defaults and <<_enabling_certain_annotations,publish only the `@PostAuthorize` method interceptor>>.
  118. The method interceptors are as follows:
  119. * For <<use-preauthorize,`@PreAuthorize`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthenticationManagerBeforeMethodInterceptor#preAuthorize`], which in turn uses {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager`]
  120. * For <<use-postauthorize,`@PostAuthorize`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.html[`AuthenticationManagerAfterMethodInterceptor#postAuthorize`], which in turn uses {security-api-url}org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.html[`PostAuthorizeAuthorizationManager`]
  121. * For <<use-prefilter,`@PreFilter`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.html[`PreFilterAuthorizationMethodInterceptor`]
  122. * For <<use-postfilter,`@PostFilter`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.html[`PostFilterAuthorizationMethodInterceptor`]
  123. * For <<use-secured,`@Secured`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthenticationManagerBeforeMethodInterceptor#secured`], which in turn uses {security-api-url}org/springframework/security/authorization/method/SecuredAuthorizationManager.html[`SecuredAuthorizationManager`]
  124. * For JSR-250 annotations, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthenticationManagerBeforeMethodInterceptor#jsr250`], which in turn uses {security-api-url}org/springframework/security/authorization/method/Jsr250AuthorizationManager.html[`Jsr250AuthorizationManager`]
  125. Generally speaking, you can consider the following listing as representative of what interceptors Spring Security publishes when you add `@EnableMethodSecurity`:
  126. ====
  127. .Java
  128. [source,java,role="primary"]
  129. ----
  130. @Bean
  131. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  132. static Advisor preAuthorizeMethodInterceptor() {
  133. return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
  134. }
  135. @Bean
  136. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  137. static Advisor postAuthorizeMethodInterceptor() {
  138. return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
  139. }
  140. @Bean
  141. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  142. static Advisor preFilterMethodInterceptor() {
  143. return AuthorizationManagerBeforeMethodInterceptor.preFilter();
  144. }
  145. @Bean
  146. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  147. static Advisor postFilterMethodInterceptor() {
  148. return AuthorizationManagerAfterMethodInterceptor.postFilter();
  149. }
  150. ----
  151. ====
  152. [[favor-granting-authorities]]
  153. === Favor Granting Authorities Over Complicated SpEL Expressions
  154. Quite often it can be tempting to introduce a complicated SpEL expression like the following:
  155. ====
  156. .Java
  157. [source,java,role="primary"]
  158. ----
  159. @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
  160. ----
  161. .Kotlin
  162. [source,kotlin,role="kotlin"]
  163. ----
  164. @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
  165. ----
  166. ====
  167. However, you could instead grant `permission:read` to those with `ROLE_ADMIN`.
  168. One way to do this is with a `RoleHierarchy` like so:
  169. ====
  170. .Java
  171. [source,java,role="primary"]
  172. ----
  173. @Bean
  174. static RoleHierarchy roleHierarchy() {
  175. return new RoleHierarchyImpl("ROLE_ADMIN > permission:read");
  176. }
  177. ----
  178. .Kotlin
  179. [source,java,role="secondary"]
  180. ----
  181. companion object {
  182. @Bean
  183. fun roleHierarchy(): RoleHierarchy {
  184. return RoleHierarchyImpl("ROLE_ADMIN > permission:read")
  185. }
  186. }
  187. ----
  188. .Xml
  189. [source,xml,role="secondary"]
  190. ----
  191. <bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
  192. <constructor-arg value="ROLE_ADMIN > permission:read"/>
  193. </bean>
  194. ----
  195. ====
  196. and then <<customizing-expression-handling,set that in a `MethodSecurityExpressionHandler` instance>>.
  197. This then allows you to have a simpler <<use-preauthorize,`@PreAuthorize`>> expression like this one:
  198. ====
  199. .Java
  200. [source,java,role="primary"]
  201. ----
  202. @PreAuthorize("hasAuthority('permission:read')")
  203. ----
  204. .Kotlin
  205. [source,kotlin,role="secondary"]
  206. ----
  207. @PreAuthorize("hasAuthority('permission:read')")
  208. ----
  209. ====
  210. Or, where possible, adapt application-specific authorization logic into granted authorities at login time.
  211. [[request-vs-method]]
  212. == Comparing Request-level vs Method-level Authorization
  213. When should you favor method-level authorization over xref:servlet/authorization/authorize-http-requests.adoc[request-level authorization]?
  214. Some of it comes down to taste; however, consider the following strengths list of each to help you decide.
  215. |===
  216. || *request-level* | *method-level*
  217. | *authorization type* | coarse-grained | fine-grained
  218. | *configuration location* | declared in a config class | local to method declaration
  219. | *configuration style* | DSL | Annotations
  220. | *authorization definitions* | programmatic | SpEL
  221. |===
  222. The main tradeoff seems to be where you want your authorization rules to live.
  223. [NOTE]
  224. It's important to remember that when you use annotation-based Method Security, then unannotated methods are not secured.
  225. To protect against this, declare xref:servlet/authorization/authorize-http-requests.adoc#activate-request-security[a catch-all authorization rule] in your xref:servlet/configuration/java.adoc#jc-httpsecurity[`HttpSecurity`] instance.
  226. [[authorizing-with-annotations]]
  227. == Authorizing with Annotations
  228. The primary way Spring Security enables method-level authorization support is through annotations that you can add to methods, classes, and interfaces.
  229. [[use-preauthorize]]
  230. === Authorizing Method Invocation with `@PreAuthorize`
  231. When <<activate-method-security,Method Security is active>>, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PreAuthorize.html[`@PreAuthorize`] annotation like so:
  232. ====
  233. .Java
  234. [source,java,role="primary"]
  235. ----
  236. @Component
  237. public class BankService {
  238. @PreAuthorize("hasRole('ADMIN')")
  239. public Account readAccount(Long id) {
  240. // ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
  241. }
  242. }
  243. ----
  244. .Kotlin
  245. [source,kotlin,role="secondary"]
  246. ----
  247. @Component
  248. open class BankService {
  249. @PreAuthorize("hasRole('ADMIN')")
  250. fun readAccount(val id: Long): Account {
  251. // ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
  252. }
  253. }
  254. ----
  255. ====
  256. This is meant to indicate that the method can only be invoked if the provided expression `hasRole('ADMIN')` passes.
  257. You can then xref:servlet/test/method.adoc[test the class] to confirm it is enforcing the authorization rule like so:
  258. ====
  259. .Java
  260. [source,java,role="primary"]
  261. ----
  262. @Autowired
  263. BankService bankService;
  264. @WithMockUser(roles="ADMIN")
  265. @Test
  266. void readAccountWithAdminRoleThenInvokes() {
  267. Account account = this.bankService.readAccount("12345678");
  268. // ... assertions
  269. }
  270. @WithMockUser(roles="WRONG")
  271. @Test
  272. void readAccountWithWrongRoleThenAccessDenied() {
  273. assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(
  274. () -> this.bankService.readAccount("12345678"));
  275. }
  276. ----
  277. .Kotlin
  278. [source,kotlin,role="secondary"]
  279. ----
  280. @WithMockUser(roles="ADMIN")
  281. @Test
  282. fun readAccountWithAdminRoleThenInvokes() {
  283. val account: Account = this.bankService.readAccount("12345678")
  284. // ... assertions
  285. }
  286. @WithMockUser(roles="WRONG")
  287. @Test
  288. fun readAccountWithWrongRoleThenAccessDenied() {
  289. assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy {
  290. this.bankService.readAccount("12345678")
  291. }
  292. }
  293. ----
  294. ====
  295. [TIP]
  296. `@PreAuthorize` also can be a <<meta-annotations, meta-annotation>>, be defined <<class-or-interface-annotations,at the class or interface level>>, and use <<authorization-expressions, SpEL Authorization Expressions>>.
  297. While `@PreAuthorize` is quite helpful for declaring needed authorities, it can also be used to evaluate more complex <<using_method_parameters,expressions that involve the method parameters>>.
  298. asdf
  299. The above two snippets are ensuring that the user can only request orders that belong to them by comparing the username parameter to xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication#getName`].
  300. The result is that the above method will only be invoked if the `username` in the request path matches the logged-in user's `name`.
  301. If not, Spring Security will throw an `AccessDeniedException` and return a 403 status code.
  302. [[use-postauthorize]]
  303. === Authorization Method Results with `@PostAuthorize`
  304. When Method Security is active, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PostAuthorize.html[`@PostAuthorize`] annotation like so:
  305. ====
  306. .Java
  307. [source,java,role="primary"]
  308. ----
  309. @Component
  310. public class BankService {
  311. @PostAuthorize("returnObject.owner == authentication.name")
  312. public Account readAccount(Long id) {
  313. // ... is only returned if the `Account` belongs to the logged in user
  314. }
  315. }
  316. ----
  317. .Kotlin
  318. [source,kotlin,role="secondary"]
  319. ----
  320. @Component
  321. open class BankService {
  322. @PostAuthorize("returnObject.owner == authentication.name")
  323. fun readAccount(val id: Long): Account {
  324. // ... is only returned if the `Account` belongs to the logged in user
  325. }
  326. }
  327. ----
  328. ====
  329. This is meant to indicate that the method can only return the value if the provided expression `returnObject.owner == authentication.name` passes.
  330. `returnObject` represents the `Account` object to be returned.
  331. You can then xref:servlet/test/method.adoc[test the class] to confirm it is enforcing the authorization rule:
  332. ====
  333. .Java
  334. [source,java,role="primary"]
  335. ----
  336. @Autowired
  337. BankService bankService;
  338. @WithMockUser(username="owner")
  339. @Test
  340. void readAccountWhenOwnedThenReturns() {
  341. Account account = this.bankService.readAccount("12345678");
  342. // ... assertions
  343. }
  344. @WithMockUser(username="wrong")
  345. @Test
  346. void readAccountWhenNotOwnedThenAccessDenied() {
  347. assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(
  348. () -> this.bankService.readAccount("12345678"));
  349. }
  350. ----
  351. .Kotlin
  352. [source,kotlin,role="secondary"]
  353. ----
  354. @WithMockUser(username="owner")
  355. @Test
  356. fun readAccountWhenOwnedThenReturns() {
  357. val account: Account = this.bankService.readAccount("12345678")
  358. // ... assertions
  359. }
  360. @WithMockUser(username="wrong")
  361. @Test
  362. fun readAccountWhenNotOwnedThenAccessDenied() {
  363. assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy {
  364. this.bankService.readAccount("12345678")
  365. }
  366. }
  367. ----
  368. ====
  369. [TIP]
  370. `@PostAuthorize` also can be a <<meta-annotations,meta-annotation>>, be defined <<class-or-interface-annotations,at the class or interface level>>, and use <<authorization-expressions, SpEL Authorization Expressions>>.
  371. `@PostAuthorize` is particularly helpful when defending against https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html[Insecure Direct Object Reference].
  372. In fact, it can be defined as a <<meta-annotations,meta-annotation>> like so:
  373. ====
  374. .Java
  375. [source,java,role="primary"]
  376. ----
  377. @Target({ ElementType.METHOD, ElementType.TYPE })
  378. @Retention(RetentionPolicy.RUNTIME)
  379. @PostAuthorize("returnObject.owner == authentication.name")
  380. public @interface RequireOwnership {}
  381. ----
  382. .Kotlin
  383. [source,kotlin,role="secondary"]
  384. ----
  385. @Target(ElementType.METHOD, ElementType.TYPE)
  386. @Retention(RetentionPolicy.RUNTIME)
  387. @PostAuthorize("returnObject.owner == authentication.name")
  388. annotation class RequireOwnership
  389. ----
  390. ====
  391. Allowing you to instead annotate the service in the following way:
  392. ====
  393. .Java
  394. [source,java,role="primary"]
  395. ----
  396. @Component
  397. public class BankService {
  398. @RequireOwnership
  399. public Account readAccount(Long id) {
  400. // ... is only returned if the `Account` belongs to the logged in user
  401. }
  402. }
  403. ----
  404. .Kotlin
  405. [source,kotlin,role="secondary"]
  406. ----
  407. @Component
  408. open class BankService {
  409. @RequireOwnership
  410. fun readAccount(val id: Long): Account {
  411. // ... is only returned if the `Account` belongs to the logged in user
  412. }
  413. }
  414. ----
  415. ====
  416. The result is that the above method will only return the `Account` if its `owner` attribute matches the logged-in user's `name`.
  417. If not, Spring Security will throw an `AccessDeniedException` and return a 403 status code.
  418. [[use-prefilter]]
  419. === Filtering Method Parameters with `@PreFilter`
  420. [NOTE]
  421. `@PreFilter` is not yet supported for Kotlin-specific data types; for that reason, only Java snippets are shown
  422. When Method Security is active, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PreFilter.html[`@PreFilter`] annotation like so:
  423. ====
  424. .Java
  425. [source,java,role="primary"]
  426. ----
  427. @Component
  428. public class BankService {
  429. @PreFilter("filterObject.owner == authentication.name")
  430. public Collection<Account> updateAccounts(Account... accounts) {
  431. // ... `accounts` will only contain the accounts owned by the logged-in user
  432. return updated;
  433. }
  434. }
  435. ----
  436. ====
  437. This is meant to filter out any values from `accounts` where the expression `filterObject.owner == authentication.name` fails.
  438. `filterObject` represents each `account` in `accounts` and is used to test each `account`.
  439. You can then test the class in the following way to confirm it is enforcing the authorization rule:
  440. ====
  441. .Java
  442. [source,java,role="primary"]
  443. ----
  444. @Autowired
  445. BankService bankService;
  446. @WithMockUser(username="owner")
  447. @Test
  448. void updateAccountsWhenOwnedThenReturns() {
  449. Account ownedBy = ...
  450. Account notOwnedBy = ...
  451. Collection<Account> updated = this.bankService.updateAccounts(ownedBy, notOwnedBy);
  452. assertThat(updated).containsOnly(ownedBy);
  453. }
  454. ----
  455. ====
  456. [TIP]
  457. `@PreFilter` also can be a <<meta-annotations,meta-annotation>>, be defined <<class-or-interface-annotations,at the class or interface level>>, and use <<authorization-expressions, SpEL Authorization Expressions>>.
  458. `@PreFilter` supports arrays, collections, maps, and streams (so long as the stream is still open).
  459. For example, the above `updateAccounts` declaration will function the same way as the following other four:
  460. ====
  461. .Java
  462. [source,java,role="primary"]
  463. ----
  464. @PreFilter("filterObject.owner == authentication.name")
  465. public Collection<Account> updateAccounts(Account[] accounts)
  466. @PreFilter("filterObject.owner == authentication.name")
  467. public Collection<Account> updateAccounts(Collection<Account> accounts)
  468. @PreFilter("filterObject.value.owner == authentication.name")
  469. public Collection<Account> updateAccounts(Map<String, Account> accounts)
  470. @PreFilter("filterObject.owner == authentication.name")
  471. public Collection<Account> updateAccounts(Stream<Account> accounts)
  472. ----
  473. ====
  474. The result is that the above method will only have the `Account` instances where their `owner` attribute matches the logged-in user's `name`.
  475. [[use-postfilter]]
  476. === Filtering Method Results with `@PostFilter`
  477. [NOTE]
  478. `@PostFilter` is not yet supported for Kotlin-specific data types; for that reason, only Java snippets are shown
  479. When Method Security is active, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PostFilter.html[`@PostFilter`] annotation like so:
  480. ====
  481. .Java
  482. [source,java,role="primary"]
  483. ----
  484. @Component
  485. public class BankService {
  486. @PostFilter("filterObject.owner == authentication.name")
  487. public Collection<Account> readAccounts(String... ids) {
  488. // ... the return value will be filtered to only contain the accounts owned by the logged-in user
  489. return accounts;
  490. }
  491. }
  492. ----
  493. ====
  494. This is meant to filter out any values from the return value where the expression `filterObject.owner == authentication.name` fails.
  495. `filterObject` represents each `account` in `accounts` and is used to test each `account`.
  496. You can then test the class like so to confirm it is enforcing the authorization rule:
  497. ====
  498. .Java
  499. [source,java,role="primary"]
  500. ----
  501. @Autowired
  502. BankService bankService;
  503. @WithMockUser(username="owner")
  504. @Test
  505. void readAccountsWhenOwnedThenReturns() {
  506. Collection<Account> accounts = this.bankService.updateAccounts("owner", "not-owner");
  507. assertThat(accounts).hasSize(1);
  508. assertThat(accounts.get(0).getOwner()).isEqualTo("owner");
  509. }
  510. ----
  511. ====
  512. [TIP]
  513. `@PostFilter` also can be a <<meta-annotations,meta-annotation>>, be defined <<class-or-interface-annotations,at the class or interface level>>, and use <<authorization-expressions, SpEL Authorization Expressions>>.
  514. `@PostFilter` supports arrays, collections, maps, and streams (so long as the stream is still open).
  515. For example, the above `readAccounts` declaration will function the same way as the following other three:
  516. ```java
  517. @PostFilter("filterObject.owner == authentication.name")
  518. public Account[] readAccounts(String... ids)
  519. @PostFilter("filterObject.value.owner == authentication.name")
  520. public Map<String, Account> readAccounts(String... ids)
  521. @PostFilter("filterObject.owner == authentication.name")
  522. public Stream<Account> readAccounts(String... ids)
  523. ```
  524. The result is that the above method will return the `Account` instances where their `owner` attribute matches the logged-in user's `name`.
  525. [NOTE]
  526. In-memory filtering can obviously be expensive, and so be considerate of whether it is better to xref:servlet/integrations/data.adoc[filter the data in the data layer] instead.
  527. [[use-secured]]
  528. === Authorizing Method Invocation with `@Secured`
  529. {security-api-url}org/springframework/security/access/annotation/Secured.html[`@Secured`] is a legacy option for authorizing invocations.
  530. <<use-preauthorize,`@PreAuthorize`>> supercedes it and is recommended instead.
  531. To use the `@Secured` annotation, you should first change your Method Security declaration to enable it like so:
  532. ====
  533. .Java
  534. [source,java,role="primary"]
  535. ----
  536. @EnableMethodSecurity(securedEnabled = true)
  537. ----
  538. .Kotlin
  539. [source,kotlin,role="secondary"]
  540. ----
  541. @EnableMethodSecurity(securedEnabled = true)
  542. ----
  543. .Xml
  544. [source,xml,role="secondary"]
  545. ----
  546. <sec:method-security secured-enabled="true"/>
  547. ----
  548. ====
  549. This will cause Spring Security to publish <<annotation-method-interceptors,the corresponding method interceptor>> that authorizes methods, classes, and interfaces annotated with `@Secured`.
  550. [[use-jsr250]]
  551. === Authorizing Method Invocation with JSR-250 Annotations
  552. In case you would like to use https://jcp.org/en/jsr/detail?id=250[JSR-250] annotations, Spring Security also supports that.
  553. <<use-preauthorize,`@PreAuthorize`>> has more expressive power and is thus recommended.
  554. To use the JSR-250 annotations, you should first change your Method Security declaration to enable them like so:
  555. ====
  556. .Java
  557. [source,java,role="primary"]
  558. ----
  559. @EnableMethodSecurity(jsr250Enabled = true)
  560. ----
  561. .Kotlin
  562. [source,kotlin,role="secondary"]
  563. ----
  564. @EnableMethodSecurity(jsr250Enabled = true)
  565. ----
  566. .Xml
  567. [source,xml,role="secondary"]
  568. ----
  569. <sec:method-security jsr250-enabled="true"/>
  570. ----
  571. ====
  572. This will cause Spring Security to publish <<annotation-method-interceptors,the corresponding method interceptor>> that authorizes methods, classes, and interfaces annotated with `@RolesAllowed`, `@PermitAll`, and `@DenyAll`.
  573. [[class-or-interface-annotations]]
  574. === Declaring Annotations at the Class or Interface Level
  575. It's also supported to have Method Security annotations at the class and interface level.
  576. If it is at the class level like so:
  577. ====
  578. .Java
  579. [source,java,role="primary"]
  580. ----
  581. @Controller
  582. @PreAuthorize("hasAuthority('ROLE_USER')")
  583. public class MyController {
  584. @GetMapping("/endpoint")
  585. public String endpoint() { ... }
  586. }
  587. ----
  588. .Kotlin
  589. [source,kotlin,role="secondary"]
  590. ----
  591. @Controller
  592. @PreAuthorize("hasAuthority('ROLE_USER')")
  593. open class MyController {
  594. @GetMapping("/endpoint")
  595. fun endpoint(): String { ... }
  596. }
  597. ----
  598. ====
  599. then all methods inherit the class-level behavior.
  600. Or, if it's declared like the following at both the class and method level:
  601. ====
  602. .Java
  603. [source,java,role="primary"]
  604. ----
  605. @Controller
  606. @PreAuthorize("hasAuthority('ROLE_USER')")
  607. public class MyController {
  608. @GetMapping("/endpoint")
  609. @PreAuthorize("hasAuthority('ROLE_ADMIN')")
  610. public String endpoint() { ... }
  611. }
  612. ----
  613. .Kotlin
  614. [source,kotlin,role="secondary"]
  615. ----
  616. @Controller
  617. @PreAuthorize("hasAuthority('ROLE_USER')")
  618. open class MyController {
  619. @GetMapping("/endpoint")
  620. @PreAuthorize("hasAuthority('ROLE_ADMIN')")
  621. fun endpoint(): String { ... }
  622. }
  623. ----
  624. ====
  625. then methods declaring the annotation override the class-level annotation.
  626. The same is true for interfaces, with the exception that if a class inherits the annotation from two different interfaces, then startup will fail.
  627. This is because Spring Security has no way to tell which one you want to use.
  628. In cases like this, you can resolve the ambiguity by adding the annotation to the concrete method.
  629. [[meta-annotations]]
  630. === Using Meta Annotations
  631. Method Security supports meta annotations.
  632. This means that you can take any annotation and improve readability based on your application-specific use cases.
  633. For example, you can simplify `@PreAuthorize("hasRole('ADMIN')")` to `@IsAdmin` like so:
  634. ====
  635. .Java
  636. [source,java,role="primary"]
  637. ----
  638. @Target({ ElementType.METHOD, ElementType.TYPE })
  639. @Retention(RetentionPolicy.RUNTIME)
  640. @PreAuthorize("hasRole('ADMIN')")
  641. public @interface IsAdmin {}
  642. ----
  643. .Kotlin
  644. [source,kotlin,role="secondary"]
  645. ----
  646. @Target(ElementType.METHOD, ElementType.TYPE)
  647. @Retention(RetentionPolicy.RUNTIME)
  648. @PreAuthorize("hasRole('ADMIN')")
  649. annotation class IsAdmin
  650. ----
  651. ====
  652. And the result is that on your secured methods you can now do the following instead:
  653. ====
  654. .Java
  655. [source,java,role="primary"]
  656. ----
  657. @Component
  658. public class BankService {
  659. @IsAdmin
  660. public Account readAccount(Long id) {
  661. // ... is only returned if the `Account` belongs to the logged in user
  662. }
  663. }
  664. ----
  665. .Kotlin
  666. [source,kotlin,role="secondary"]
  667. ----
  668. @Component
  669. open class BankService {
  670. @IsAdmin
  671. fun readAccount(val id: Long): Account {
  672. // ... is only returned if the `Account` belongs to the logged in user
  673. }
  674. }
  675. ----
  676. ====
  677. This results in more readable method definitions.
  678. [[enable-annotation]]
  679. === Enabling Certain Annotations
  680. You can turn off ``@EnableMethodSecurity``'s pre-configuration and replace it with you own.
  681. You may choose to do this if you want to <<custom-authorization-managers,customize the `AuthorizationManager`>> or `Pointcut`.
  682. Or you may simply want to only enable a specific annotation, like `@PostAuthorize`.
  683. You can do this in the following way:
  684. .Only @PostAuthorize Configuration
  685. ====
  686. .Java
  687. [source,java,role="primary"]
  688. ----
  689. @Configuration
  690. @EnableMethodSecurity(prePostEnabled = false)
  691. class MethodSecurityConfig {
  692. @Bean
  693. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  694. Advisor postAuthorize() {
  695. return AuthorizationManagerBeforeMethodInterceptor.postAuthorize();
  696. }
  697. }
  698. ----
  699. .Kotlin
  700. [source,kotlin,role="secondary"]
  701. ----
  702. @Configuration
  703. @EnableMethodSecurity(prePostEnabled = false)
  704. class MethodSecurityConfig {
  705. @Bean
  706. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  707. fun postAuthorize() : Advisor {
  708. return AuthorizationManagerBeforeMethodInterceptor.postAuthorize()
  709. }
  710. }
  711. ----
  712. .Xml
  713. [source,xml,role="secondary"]
  714. ----
  715. <sec:method-security pre-post-enabled="false"/>
  716. <aop:config/>
  717. <bean id="postAuthorize"
  718. class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
  719. factory-method="postAuthorize"/>
  720. ----
  721. ====
  722. The above snippet achieves this by first disabling Method Security's pre-configurations and then publishing <<annotation-method-interceptors, the `@PostAuthorize` interceptor>> itself.
  723. [[use-intercept-methods]]
  724. == Authorizing with `<intercept-methods>`
  725. While using Spring Security's <<authorizing-with-annotations,annotation-based support>> is preferred for method security, you can also use XML to declare bean authorization rules.
  726. If you need to declare it in your XML configuration instead, you can use xref:servlet/appendix/namespace/method-security.adoc#nsa-intercept-methods[`<intercept-methods>`] like so:
  727. ====
  728. .Xml
  729. [source,xml,role="primary"]
  730. ----
  731. <bean class="org.mycompany.MyController">
  732. <intercept-methods>
  733. <protect method="get*" access="hasAuthority('read')"/>
  734. <protect method="*" access="hasAuthority('write')"/>
  735. </intercept-methods>
  736. </bean>
  737. ----
  738. ====
  739. [NOTE]
  740. This only supports matching method by prefix or by name.
  741. If your needs are more complex than that, <<authorizing-with-annotations,use annotation support>> instead.
  742. [[use-programmatic-authorization]]
  743. == Authorizing Methods Programmatically
  744. As you've already seen, there are several ways that you can specify non-trivial authorization rules using <<authorization-expressions, Method Security SpEL expressions>>.
  745. There are a number of ways that you can instead allow your logic to be Java-based instead of SpEL-based.
  746. This gives use access the entire Java language for increased testability and flow control.
  747. === Using a Custom Bean in SpEL
  748. The first way to authorize a method programmatically is a two-step process.
  749. First, declare a bean that has a method that takes a `MethodSecurityExpressionOperations` instance like the following:
  750. ====
  751. .Java
  752. [source,java,role="primary"]
  753. ----
  754. @Component("authz")
  755. public class AuthorizationLogic {
  756. public boolean decide(MethodSecurityExpressionOperations operations) {
  757. // ... authorization logic
  758. }
  759. }
  760. ----
  761. .Kotlin
  762. [source,kotlin,role="secondary"]
  763. ----
  764. @Component("authz")
  765. open class AuthorizationLogic {
  766. fun decide(val operations: MethodSecurityExpressionOperations): boolean {
  767. // ... authorization logic
  768. }
  769. }
  770. ----
  771. ====
  772. Then, reference that bean in your annotations in the following way:
  773. ====
  774. .Java
  775. [source,java,role="primary"]
  776. ----
  777. @Controller
  778. public class MyController {
  779. @PreAuthorize("@authz.decide(#root)")
  780. @GetMapping("/endpoint")
  781. public String endpoint() {
  782. // ...
  783. }
  784. }
  785. ----
  786. .Kotlin
  787. [source,kotlin,role="secondary"]
  788. ----
  789. @Controller
  790. open class MyController {
  791. @PreAuthorize("@authz.decide(#root)")
  792. @GetMapping("/endpoint")
  793. fun String endpoint() {
  794. // ...
  795. }
  796. }
  797. ----
  798. ====
  799. Spring Security will invoke the given method on that bean for each method invocation.
  800. What's nice about this is all your authorization logic is in a separate class that can be independently unit tested and verified for correctness.
  801. It also has access to the full Java language.
  802. [[custom-authorization-managers]]
  803. === Using a Custom Authorization Manager
  804. The second way to authorize a method programmatically is two create a custom xref:servlet/authorization/architecture.adoc#_the_authorizationmanager[`AuthorizationManager`].
  805. First, declare an authorization manager instance, perhaps like this one:
  806. ====
  807. .Java
  808. [source,java,role="primary"]
  809. ----
  810. @Component
  811. public class MyAuthorizationManager implements AuthorizationManager<MethodInvocation> {
  812. public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
  813. // ... authorization logic
  814. }
  815. }
  816. ----
  817. .Kotlin
  818. [source,kotlin,role="secondary"]
  819. ----
  820. @Component("authz")
  821. open class MyAuthorizationManager: AuthorizationManager<MethodInvocation> {
  822. fun check(val authentication: Supplier<Authentication>, val invocation: MethodInvocation): AuthorizationDecision {
  823. // ... authorization logic
  824. }
  825. }
  826. ----
  827. ====
  828. Then, publish the method interceptor with a pointcut that corresponds to when you want that `AuthorizationManager` to run.
  829. For example, you could replace how `@PreAuthorize` and `@PostAuthorize` work like so:
  830. .Only @PostAuthorize Configuration
  831. ====
  832. .Java
  833. [source,java,role="primary"]
  834. ----
  835. @Configuration
  836. @EnableMethodSecurity(prePostEnabled = false)
  837. class MethodSecurityConfig {
  838. @Bean
  839. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  840. Advisor postAuthorize(MyAuthorizationManager manager) {
  841. return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
  842. }
  843. @Bean
  844. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  845. Advisor postAuthorize(MyAuthorizationManager manager) {
  846. return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
  847. }
  848. }
  849. ----
  850. .Kotlin
  851. [source,kotlin,role="secondary"]
  852. ----
  853. @Configuration
  854. @EnableMethodSecurity(prePostEnabled = false)
  855. class MethodSecurityConfig {
  856. @Bean
  857. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  858. fun preAuthorize(val manager: MyAuthorizationManager) : Advisor {
  859. return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager)
  860. }
  861. @Bean
  862. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  863. fun postAuthorize(val manager: MyAuthorizationManager) : Advisor {
  864. return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager)
  865. }
  866. }
  867. ----
  868. .Xml
  869. [source,xml,role="secondary"]
  870. ----
  871. <sec:method-security pre-post-enabled="false"/>
  872. <aop:config/>
  873. <bean id="postAuthorize"
  874. class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
  875. factory-method="preAuthorize">
  876. <constructor-arg ref="myAuthorizationManager"/>
  877. </bean>
  878. <bean id="postAuthorize"
  879. class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor"
  880. factory-method="postAuthorize">
  881. <constructor-arg ref="myAuthorizationManager"/>
  882. </bean>
  883. ----
  884. ====
  885. [TIP]
  886. ====
  887. You can place your interceptor in between Spring Security method interceptors using the order constants specified in `AuthorizationInterceptorsOrder`.
  888. ====
  889. [[customizing-expression-handling]]
  890. === Customizing Expression Handling
  891. Or, third, you can customize how each SpEL expression is handled.
  892. To do that, you can expose a custom {security-api-url}org.springframework.security.access.expression.method.MethodSecurityExpressionHandler.html[`MethodSecurityExpressionHandler`], like so:
  893. .Custom MethodSecurityExpressionHandler
  894. ====
  895. .Java
  896. [source,java,role="primary"]
  897. ----
  898. @Bean
  899. static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
  900. DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
  901. handler.setRoleHierarchy(roleHierarchy);
  902. return handler;
  903. }
  904. ----
  905. .Kotlin
  906. [source,kotlin,role="secondary"]
  907. ----
  908. companion object {
  909. @Bean
  910. fun methodSecurityExpressionHandler(val roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler {
  911. val handler = DefaultMethodSecurityExpressionHandler();
  912. handler.setRoleHierarchy(roleHierarchy);
  913. return handler;
  914. }
  915. }
  916. ----
  917. .Xml
  918. [source,xml,role="secondary"]
  919. ----
  920. <sec:method-security>
  921. <sec:expression-handler ref="myExpressionHandler"/>
  922. </sec:method-security>
  923. <bean id="myExpressionHandler"
  924. class="org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler">
  925. <property name="roleHierarchy" ref="roleHierarchy"/>
  926. </bean>
  927. ----
  928. ====
  929. [TIP]
  930. ====
  931. We expose `MethodSecurityExpressionHandler` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
  932. ====
  933. You can also <<subclass-defaultmethodsecurityexpressionhandler,subclass `DefaultMessageSecurityExpressionHandler`>> to add your own custom authorization expressions beyond the defaults.
  934. [[use-aspectj]]
  935. == Authorizing with AspectJ
  936. [[match-by-pointcut]]
  937. === Matching Methods with Custom Pointcuts
  938. Being built on Spring AOP, you can declare patterns that are not related to annotations, similar to xref:servlet/authorization/authorize-http-requests.adoc[request-level authorization].
  939. This has the potential advantage of centralizing method-level authorization rules.
  940. For example, you can use publish your own `Advisor` or use xref:servlet/appendix/namespace/method-security.adoc#nsa-protect-pointcut[`<protect-pointcut>`] to match AOP expressions to authorization rules for your service layer like so:
  941. ====
  942. .Java
  943. [source,java,role="primary"]
  944. ----
  945. import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
  946. @Bean
  947. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  948. static Advisor protectServicePointcut() {
  949. JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
  950. pattern.setPattern("execution(* com.mycompany.*Service.*(..))");
  951. return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"));
  952. }
  953. ----
  954. .Kotlin
  955. [source,kotlin,role="secondary"]
  956. ----
  957. import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
  958. companion object {
  959. @Bean
  960. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  961. fun protectServicePointcut(): Advisor {
  962. var pattern = JdkRegexpMethodPointcut();
  963. pattern.setPattern("execution(* com.mycompany.*Service.*(..))");
  964. return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"));
  965. }
  966. }
  967. ----
  968. [source,xml]
  969. ----
  970. <sec:method-security>
  971. <protect-pointcut expression="execution(* com.mycompany.*Service.*(..))" access="hasRole('USER')"/>
  972. </sec:method-security>
  973. ----
  974. ====
  975. [[weave-aspectj]]
  976. === Integrate with AspectJ Byte-weaving
  977. Performance can at times be enhanced by using AspectJ to weave Spring Security advice into the byte code of your beans.
  978. After setting up AspectJ, you can quite simply state in the `@EnableMethodSecurity` annotation or `<method-security>` element that you are using AspectJ:
  979. ====
  980. .Java
  981. [source,java,role="primary"]
  982. ----
  983. @EnableMethodSecurity(mode=AdviceMode.ASPECTJ)
  984. ----
  985. .Kotlin
  986. [source,kotlin,role="secondary"]
  987. ----
  988. @EnableMethodSecurity(mode=AdviceMode.ASPECTJ)
  989. ----
  990. .Xml
  991. [source,xml,role="secondary"]
  992. ----
  993. <sec:method-security mode="aspectj"/>
  994. ----
  995. ====
  996. And the result will be that Spring Security will publish its advisors as AspectJ advice so that they can be woven in accordingly.
  997. [[changing-the-order]]
  998. == Specifying Order
  999. As already noted, there is a Spring AOP method interceptor for each annotation, and each of these has a location in the Spring AOP advisor chain.
  1000. Namely, the `@PreFilter` method interceptor's order is 100, ``@PreAuthorize``'s is 200, and so on.
  1001. The reason this is important to note is that there are other AOP-based annotations like `@EnableTransactionManagement` that have an order of `Integer.MAX_VALUE`.
  1002. In other words, they are located at the end of the advisor chain by default.
  1003. At times, it can be valuable to have other advice execute before Spring Security.
  1004. For example, if you have a method annotated with `@Transactional` and `@PostAuthorize`, you might want the transaction to still be open when `@PostAuthorize` runs so that an `AccessDeniedException` will cause a rollback.
  1005. To get `@EnableTransactionManagement` to open a transaction before method authorization advice runs, you can set ``@EnableTransactionManagement``'s order like so:
  1006. ====
  1007. .Java
  1008. [source,java,role="primary"]
  1009. ----
  1010. @EnableTransactionManagement(order = 0)
  1011. ----
  1012. .Kotlin
  1013. [source,kotlin,role="secondary"]
  1014. ----
  1015. @EnableTransactionManagement(order = 0)
  1016. ----
  1017. .Xml
  1018. [source,xml,role="secondary"]
  1019. ----
  1020. <tx:annotation-driven ref="txManager" order="0"/>
  1021. ----
  1022. ====
  1023. Since the earliest method interceptor (`@PreFilter`) is set to an order of 100, a setting of zero means that the transaction advice will run before all Spring Security advice.
  1024. [[authorization-expressions]]
  1025. == Expressing Authorization with SpEL
  1026. You've already seen several examples using SpEL, so now let's cover the API a bit more in depth.
  1027. Spring Security encapsulates all of its authorization fields and methods in a set of root objects.
  1028. The most generic root object is called `SecurityExpressionRoot` and it forms the basis for `MethodSecurityExpressionRoot`.
  1029. Spring Security supplies this root object to `MethodSecurityEvaluationContext` when preparing to evaluate an authorization expression.
  1030. [[using-authorization-expression-fields-and-methods]]
  1031. === Using Authorization Expression Fields and Methods
  1032. The first thing this provides is an enhanced set of authorization fields and methods to your SpEL expressions.
  1033. What follows is a quick overview of the most common methods:
  1034. * `permitAll` - The method requires no authorization to be invoked; note that in this case, xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] is never retrieved from the session
  1035. * `denyAll` - The method is not allowed under any circumstances; note that in this case, the `Authentication` is never retrieved from the session
  1036. * `hasAuthority` - The method requires that the `Authentication` have xref:servlet/authorization/architecture.adoc#authz-authorities[a `GrantedAuthority`] that matches the given value
  1037. * `hasRole` - A shortcut for `hasAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
  1038. * `hasAnyAuthority` - The method requires that the `Authentication` have a `GrantedAuthority` that matches any of the given values
  1039. * `hasAnyRole` - A shortcut for `hasAnyAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
  1040. * `hasPermission` - A hook into your `PermissionEvaluator` instance for doing object-level authorization
  1041. And here is a brief look at the most common fields:
  1042. * `authentication` - The `Authentication` instance associated with this method invocation
  1043. * `principal` - The `Authentication#getPrincipal` associated with this method invocation
  1044. Having now learned the patterns, rules, and how they can be paired together, you should be able to understand what is going on in this more complex example:
  1045. .Authorize Requests
  1046. ====
  1047. .Java
  1048. [source,java,role="primary"]
  1049. ----
  1050. @Component
  1051. public class MyService {
  1052. @PreAuthorize("denyAll") <1>
  1053. MyResource myDeprecatedMethod(...);
  1054. @PreAuthorize("hasRole('ADMIN')") <2>
  1055. MyResource writeResource(...)
  1056. @PreAuthorize("hasAuthority('db') and hasRole('ADMIN')") <3>
  1057. MyResource deleteResource(...)
  1058. @PreAuthorize("principal.claims['aud'] == 'my-audience'") <4>
  1059. MyResource readResource(...);
  1060. @PreAuthorize("@authz.check(authentication, #root)")
  1061. MyResource shareResource(...);
  1062. }
  1063. ----
  1064. .Kotlin
  1065. [source,kotlin,role="secondary"]
  1066. ----
  1067. @Component
  1068. open class MyService {
  1069. @PreAuthorize("denyAll") <1>
  1070. fun myDeprecatedMethod(...): MyResource
  1071. @PreAuthorize("hasRole('ADMIN')") <2>
  1072. fun writeResource(...): MyResource
  1073. @PreAuthorize("hasAuthority('db') and hasRole('ADMIN')") <3>
  1074. fun deleteResource(...): MyResource
  1075. @PreAuthorize("principal.claims['aud'] == 'my-audience'") <4>
  1076. fun readResource(...): MyResource
  1077. @PreAuthorize("@authz.check(#root)")
  1078. fun shareResource(...): MyResource;
  1079. }
  1080. ----
  1081. .Xml
  1082. [source,xml,role="secondary"]
  1083. ----
  1084. <sec:method-security>
  1085. <protect-pointcut expression="execution(* com.mycompany.*Service.myDeprecatedMethod(..))" access="denyAll"/> <1>
  1086. <protect-pointcut expression="execution(* com.mycompany.*Service.writeResource(..))" access="hasRole('ADMIN')"/> <2>
  1087. <protect-pointcut expression="execution(* com.mycompany.*Service.deleteResource(..))" access="hasAuthority('db') and hasRole('ADMIN')"/> <3>
  1088. <protect-pointcut expression="execution(* com.mycompany.*Service.readResource(..))" access="principal.claims['aud'] == 'my-audience'"/> <4>
  1089. <protect-pointcut expression="execution(* com.mycompany.*Service.shareResource(..))" access="@authz.check(#root)"/> <5>
  1090. </sec:method-security>
  1091. ----
  1092. ====
  1093. <1> This method may not be invoked by anyone for any reason
  1094. <2> This method may only be invoked by ``Authentication``s granted the `ROLE_ADMIN` authority
  1095. <3> This method may only be invoked by ``Authentication``s granted the `db` and `ROLE_ADMIN` authorities
  1096. <4> This method may only be invoked by ``Princpal``s with an `aud` claim equal to "my-audience"
  1097. <5> This method may only be invoked if the bean ``authz``'s `check` method returns `true`
  1098. [[using_method_parameters]]
  1099. === Using Method Parameters
  1100. Additionally, Spring Security provides a mechanism for discovering method parameters so they can also be accessed in the SpEL expression as well.
  1101. For a complete reference, Spring Security uses `DefaultSecurityParameterNameDiscoverer` to discover the parameter names.
  1102. By default, the following options are tried for a method.
  1103. 1. If Spring Security's `@P` annotation is present on a single argument to the method, the value is used.
  1104. The following example uses the `@P` annotation:
  1105. +
  1106. ====
  1107. .Java
  1108. [source,java,role="primary"]
  1109. ----
  1110. import org.springframework.security.access.method.P;
  1111. ...
  1112. @PreAuthorize("hasPermission(#c, 'write')")
  1113. public void updateContact(@P("c") Contact contact);
  1114. ----
  1115. .Kotlin
  1116. [source,kotlin,role="secondary"]
  1117. ----
  1118. import org.springframework.security.access.method.P
  1119. ...
  1120. @PreAuthorize("hasPermission(#c, 'write')")
  1121. fun doSomething(@P("c") contact: Contact?)
  1122. ----
  1123. ====
  1124. +
  1125. The intention of this expression is to require that the current `Authentication` have `write` permission specifically for this `Contact` instance.
  1126. +
  1127. Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation.
  1128. * If xref:servlet/integrations/data.adoc[Spring Data's] `@Param` annotation is present on at least one parameter for the method, the value is used.
  1129. The following example uses the `@Param` annotation:
  1130. +
  1131. ====
  1132. .Java
  1133. [source,java,role="primary"]
  1134. ----
  1135. import org.springframework.data.repository.query.Param;
  1136. ...
  1137. @PreAuthorize("#n == authentication.name")
  1138. Contact findContactByName(@Param("n") String name);
  1139. ----
  1140. .Kotlin
  1141. [source,kotlin,role="secondary"]
  1142. ----
  1143. import org.springframework.data.repository.query.Param
  1144. ...
  1145. @PreAuthorize("#n == authentication.name")
  1146. fun findContactByName(@Param("n") name: String?): Contact?
  1147. ----
  1148. ====
  1149. +
  1150. The intention of this expression is to require that `name` be equal to `Authentication#getName` for the invocation to be authorized.
  1151. +
  1152. Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation.
  1153. * If you compile your code with the `-parameters` argument, the standard JDK reflection API is used to discover the parameter names.
  1154. This works on both classes and interfaces.
  1155. * Finally, if you compile your code with debug symbols, the parameter names are discovered by using the debug symbols.
  1156. This does not work for interfaces, since they do not have debug information about the parameter names.
  1157. For interfaces, either annotations or the `-parameters` approach must be used.
  1158. [[migration-enableglobalmethodsecurity]]
  1159. == Migrating from `@EnableGlobalMethodSecurity`
  1160. If you are using `@EnableGlobalMethodSecurity`, you should migrate to `@EnableMethodSecurity`.
  1161. [[servlet-replace-globalmethodsecurity-with-methodsecurity]]
  1162. === 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]
  1163. {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.
  1164. 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.
  1165. This means that the following two listings are functionally equivalent:
  1166. ====
  1167. .Java
  1168. [source,java,role="primary"]
  1169. ----
  1170. @EnableGlobalMethodSecurity(prePostEnabled = true)
  1171. ----
  1172. .Kotlin
  1173. [source,kotlin,role="secondary"]
  1174. ----
  1175. @EnableGlobalMethodSecurity(prePostEnabled = true)
  1176. ----
  1177. .Xml
  1178. [source,xml,role="secondary"]
  1179. ----
  1180. <global-method-security pre-post-enabled="true"/>
  1181. ----
  1182. ====
  1183. and:
  1184. ====
  1185. .Java
  1186. [source,java,role="primary"]
  1187. ----
  1188. @EnableMethodSecurity
  1189. ----
  1190. .Kotlin
  1191. [source,kotlin,role="secondary"]
  1192. ----
  1193. @EnableMethodSecurity
  1194. ----
  1195. .Xml
  1196. [source,xml,role="secondary"]
  1197. ----
  1198. <method-security/>
  1199. ----
  1200. ====
  1201. For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior.
  1202. For example, a listing like:
  1203. ====
  1204. .Java
  1205. [source,java,role="primary"]
  1206. ----
  1207. @EnableGlobalMethodSecurity(securedEnabled = true)
  1208. ----
  1209. .Kotlin
  1210. [source,kotlin,role="secondary"]
  1211. ----
  1212. @EnableGlobalMethodSecurity(securedEnabled = true)
  1213. ----
  1214. .Xml
  1215. [source,xml,role="secondary"]
  1216. ----
  1217. <global-method-security secured-enabled="true"/>
  1218. ----
  1219. ====
  1220. should change to:
  1221. ====
  1222. .Java
  1223. [source,java,role="primary"]
  1224. ----
  1225. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  1226. ----
  1227. .Kotlin
  1228. [source,kotlin,role="secondary"]
  1229. ----
  1230. @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
  1231. ----
  1232. .Xml
  1233. [source,xml,role="secondary"]
  1234. ----
  1235. <method-security secured-enabled="true" pre-post-enabled="false"/>
  1236. ----
  1237. ====
  1238. === Use a Custom `@Bean` instead of subclassing `DefaultMethodSecurityExpressionHandler`
  1239. As a performance optimization, a new method was introduced to `MethodSecurityExpressionHandler` that takes a `Supplier<Authentication>` instead of an `Authentication`.
  1240. This allows Spring Security to defer the lookup of the `Authentication`, and is taken advantage of automatically when you use `@EnableMethodSecurity` instead of `@EnableGlobalMethodSecurity`.
  1241. However, let's say that your code extends `DefaultMethodSecurityExpressionHandler` and overrides `createSecurityExpressionRoot(Authentication, MethodInvocation)` to return a custom `SecurityExpressionRoot` instance.
  1242. This will no longer work because the arrangement that `@EnableMethodSecurity` sets up calls `createEvaluationContext(Supplier<Authentication>, MethodInvocation)` instead.
  1243. Happily, such a level of customization is often unnecessary.
  1244. Instead, you can create a custom bean with the authorization methods that you need.
  1245. For example, let's say you are wanting a custom evaluation of `@PostAuthorize("hasAuthority('ADMIN')")`.
  1246. You can create a custom `@Bean` like this one:
  1247. ====
  1248. .Java
  1249. [source,java,role="primary"]
  1250. ----
  1251. class MyAuthorizer {
  1252. boolean isAdmin(MethodSecurityExpressionOperations root) {
  1253. boolean decision = root.hasAuthority("ADMIN");
  1254. // custom work ...
  1255. return decision;
  1256. }
  1257. }
  1258. ----
  1259. .Kotlin
  1260. [source,kotlin,role="secondary"]
  1261. ----
  1262. class MyAuthorizer {
  1263. fun isAdmin(val root: MethodSecurityExpressionOperations): boolean {
  1264. val decision = root.hasAuthority("ADMIN");
  1265. // custom work ...
  1266. return decision;
  1267. }
  1268. }
  1269. ----
  1270. ====
  1271. and then refer to it in the annotation like so:
  1272. ====
  1273. .Java
  1274. [source,java,role="primary"]
  1275. ----
  1276. @PreAuthorize("@authz.isAdmin(#root)")
  1277. ----
  1278. .Kotlin
  1279. [source,kotlin,role="secondary"]
  1280. ----
  1281. @PreAuthorize("@authz.isAdmin(#root)")
  1282. ----
  1283. ====
  1284. [[subclass-defaultmethodsecurityexpressionhandler]]
  1285. ==== I'd still prefer to subclass `DefaultMethodSecurityExpressionHandler`
  1286. If you must continue subclassing `DefaultMethodSecurityExpressionHandler`, you can still do so.
  1287. Instead, override the `createEvaluationContext(Supplier<Authentication>, MethodInvocation)` method like so:
  1288. ====
  1289. .Java
  1290. [source,java,role="primary"]
  1291. ----
  1292. @Component
  1293. class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
  1294. @Override
  1295. public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
  1296. StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
  1297. MethodSecurityExpressionOperations delegate = (MethodSecurityExpressionOperations) context.getRootObject().getValue();
  1298. MySecurityExpressionRoot root = new MySecurityExpressionRoot(delegate);
  1299. context.setRootObject(root);
  1300. return context;
  1301. }
  1302. }
  1303. ----
  1304. .Kotlin
  1305. [source,kotlin,role="secondary"]
  1306. ----
  1307. @Component
  1308. class MyExpressionHandler: DefaultMethodSecurityExpressionHandler {
  1309. override fun createEvaluationContext(val authentication: Supplier<Authentication>,
  1310. val mi: MethodInvocation): EvaluationContext {
  1311. val context = super.createEvaluationContext(authentication, mi) as StandardEvaluationContext
  1312. val delegate = context.getRootObject().getValue() as MethodSecurityExpressionOperations
  1313. val root = MySecurityExpressionRoot(delegate)
  1314. context.setRootObject(root);
  1315. return context;
  1316. }
  1317. }
  1318. ----
  1319. ====
  1320. == Further Reading
  1321. Now that you have secured your application's requests, please xref:servlet/authorization/authorize-http-requests.adoc[secure its requests] if you haven't already.
  1322. You can also read further on xref:servlet/test/index.adoc[testing your application] or on integrating Spring Security with other aspects of you application like xref:servlet/integrations/data.adoc[the data layer] or xref:servlet/integrations/observability.adoc[tracing and metrics].