123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782 |
- [[jc-method]]
- = Method Security
- :figures: servlet/authorization
- 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.
- [[activate-method-security]]
- 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:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @EnableMethodSecurity
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @EnableMethodSecurity
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <sec:method-security/>
- ----
- ======
- 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.
- [NOTE]
- {spring-boot-reference-url}using.html#using.build-systems.starters[Spring Boot Starter Security] does not activate method-level authorization by default.
- Method Security supports many other use cases as well including <<use-aspectj, AspectJ support>>, <<use-programmatic-authorization,custom annotations>>, and several configuration points.
- Consider learning about the following use cases:
- * <<migration-enableglobalmethodsecurity, Migrating from `@EnableGlobalMethodSecurity`>>
- * Understanding <<method-security-architecture,how method security works>> and reasons to use it
- * Comparing <<request-vs-method,request-level and method-level authorization>>
- * Authorizing methods with <<use-preauthorize,`@PreAuthorize`>> and <<use-postauthorize,`@PostAuthorize`>>
- * Filtering methods with <<use-prefilter,`@PreFilter`>> and <<use-postfilter,`@PostFilter`>>
- * Authorizing methods with <<use-jsr250,JSR-250 annotations>>
- * Authorizing methods with <<use-aspectj,AspectJ expressions>>
- * Integrating with <<weave-aspectj,AspectJ byte-code weaving>>
- * Coordinating with <<changing-the-order,@Transactional and other AOP-based annotations>>
- * Customizing <<customizing-expression-handling,SpEL expression handling>>
- * Integrating with <<custom-authorization-managers,custom authorization systems>>
- [[method-security-architecture]]
- == How Method Security Works
- Spring Security's method authorization support is handy for:
- * Extracting fine-grained authorization logic; for example, when the method parameters and return values contribute to the authorization decision.
- * Enforcing security at the service layer
- * Stylistically favoring annotation-based over `HttpSecurity`-based configuration
- 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.
- As already mentioned, you begin by adding `@EnableMethodSecurity` to a `@Configuration` class or `<sec:method-security/>` in a Spring XML configuration file.
- [[use-method-security]]
- [NOTE]
- ====
- This annotation and XML element supercede `@EnableGlobalMethodSecurity` and `<sec:global-method-security/>`, respectively.
- They offer the following improvements:
- 1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters.
- This simplifies reuse and customization.
- 2. Favors direct bean-based configuration, instead of requiring extending `GlobalMethodSecurityConfiguration` to customize beans
- 3. Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize
- 4. Checks for conflicting annotations to ensure an unambiguous security configuration
- 5. Complies with JSR-250
- 6. Enables `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` by default
- If you are using `@EnableGlobalMethodSecurity` or `<global-method-security/>`, these are now deprecated, and you are encouraged to migrate.
- ====
- Method authorization is a combination of before- and after-method authorization.
- Consider a service bean that is annotated in the following way:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Service
- public class MyCustomerService {
- @PreAuthorize("hasAuthority('permission:read')")
- @PostAuthorize("returnObject.owner == authentication.name")
- public Customer readCustomer(String id) { ... }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Service
- open class MyCustomerService {
- @PreAuthorize("hasAuthority('permission:read')")
- @PostAuthorize("returnObject.owner == authentication.name")
- fun readCustomer(val id: String): Customer { ... }
- }
- ----
- ======
- A given invocation to `MyCustomerService#readCustomer` may look something like this when Method Security <<activate-method-security,is activated>>:
- image::{figures}/methodsecurity.png[]
- 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>>
- 2. The interceptor invokes {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager#check`]
- 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`.
- 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]
- 5. If the evaluation passes, then Spring AOP proceeds to invoke the method.
- 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
- 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`]
- 8. If the evaluation passes (in this case, the return value belongs to the logged-in user), processing continues normally
- 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
- [NOTE]
- If the method is not being called in the context of an HTTP request, you will likely need to handle the `AccessDeniedException` yourself
- [[unanimous-based-authorization-decisions]]
- === Multiple Annotations Are Computed In Series
- As demonstrated above, if a method invocation involves multiple <<authorizing-with-annotations,Method Security annotations>>, each of those is processed one at a time.
- This means that they can collectively be thought of as being "anded" together.
- In other words, for an invocation to be authorized, all annotation inspections need to pass authorization.
- [[repeated-annotations]]
- === Repeated Annotations Are Not Supported
- That said, it is not supported to repeat the same annotation on the same method.
- For example, you cannot place `@PreAuthorize` twice on the same method.
- Instead, use SpEL's boolean support or its support for delegating to a separate bean.
- [[annotation-method-pointcuts]]
- === Each Annotation Has Its Own Pointcut
- 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>>.
- You can see the specifics of this in {security-api-url}org/springframework/security/authorization/method/AuthorizationMethodPointcuts.html[`AuthorizationMethodPointcuts`].
- [[annotation-method-interceptors]]
- === Each Annotation Has Its Own Method Interceptor
- Each annotation has its own dedicated method interceptor.
- The reason for this is to make things more composable.
- For example, if needed, you can disable the Spring Security defaults and <<_enabling_certain_annotations,publish only the `@PostAuthorize` method interceptor>>.
- The method interceptors are as follows:
- * 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`]
- * 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`]
- * For <<use-prefilter,`@PreFilter`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.html[`PreFilterAuthorizationMethodInterceptor`]
- * For <<use-postfilter,`@PostFilter`>>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.html[`PostFilterAuthorizationMethodInterceptor`]
- * 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`]
- * 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`]
- Generally speaking, you can consider the following listing as representative of what interceptors Spring Security publishes when you add `@EnableMethodSecurity`:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- static Advisor preAuthorizeMethodInterceptor() {
- return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
- }
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- static Advisor postAuthorizeMethodInterceptor() {
- return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
- }
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- static Advisor preFilterMethodInterceptor() {
- return AuthorizationManagerBeforeMethodInterceptor.preFilter();
- }
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- static Advisor postFilterMethodInterceptor() {
- return AuthorizationManagerAfterMethodInterceptor.postFilter();
- }
- ----
- ======
- [[favor-granting-authorities]]
- === Favor Granting Authorities Over Complicated SpEL Expressions
- Quite often it can be tempting to introduce a complicated SpEL expression like the following:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
- ----
- ======
- .Kotlin
- [source,kotlin,role="kotlin"]
- ----
- @PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
- ----
- However, you could instead grant `permission:read` to those with `ROLE_ADMIN`.
- One way to do this is with a `RoleHierarchy` like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Bean
- static RoleHierarchy roleHierarchy() {
- return new RoleHierarchyImpl("ROLE_ADMIN > permission:read");
- }
- ----
- Kotlin::
- +
- [source,java,role="secondary"]
- ----
- companion object {
- @Bean
- fun roleHierarchy(): RoleHierarchy {
- return RoleHierarchyImpl("ROLE_ADMIN > permission:read")
- }
- }
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
- <constructor-arg value="ROLE_ADMIN > permission:read"/>
- </bean>
- ----
- ======
- and then <<customizing-expression-handling,set that in a `MethodSecurityExpressionHandler` instance>>.
- This then allows you to have a simpler <<use-preauthorize,`@PreAuthorize`>> expression like this one:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @PreAuthorize("hasAuthority('permission:read')")
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @PreAuthorize("hasAuthority('permission:read')")
- ----
- ======
- Or, where possible, adapt application-specific authorization logic into granted authorities at login time.
- [[request-vs-method]]
- == Comparing Request-level vs Method-level Authorization
- When should you favor method-level authorization over xref:servlet/authorization/authorize-http-requests.adoc[request-level authorization]?
- Some of it comes down to taste; however, consider the following strengths list of each to help you decide.
- |===
- || *request-level* | *method-level*
- | *authorization type* | coarse-grained | fine-grained
- | *configuration location* | declared in a config class | local to method declaration
- | *configuration style* | DSL | Annotations
- | *authorization definitions* | programmatic | SpEL
- |===
- The main tradeoff seems to be where you want your authorization rules to live.
- [NOTE]
- It's important to remember that when you use annotation-based Method Security, then unannotated methods are not secured.
- 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.
- [[authorizing-with-annotations]]
- == Authorizing with Annotations
- The primary way Spring Security enables method-level authorization support is through annotations that you can add to methods, classes, and interfaces.
- [[use-preauthorize]]
- === Authorizing Method Invocation with `@PreAuthorize`
- 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:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- public class BankService {
- @PreAuthorize("hasRole('ADMIN')")
- public Account readAccount(Long id) {
- // ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Component
- open class BankService {
- @PreAuthorize("hasRole('ADMIN')")
- fun readAccount(val id: Long): Account {
- // ... is only invoked if the `Authentication` has the `ROLE_ADMIN` authority
- }
- }
- ----
- ======
- This is meant to indicate that the method can only be invoked if the provided expression `hasRole('ADMIN')` passes.
- You can then xref:servlet/test/method.adoc[test the class] to confirm it is enforcing the authorization rule like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Autowired
- BankService bankService;
- @WithMockUser(roles="ADMIN")
- @Test
- void readAccountWithAdminRoleThenInvokes() {
- Account account = this.bankService.readAccount("12345678");
- // ... assertions
- }
- @WithMockUser(roles="WRONG")
- @Test
- void readAccountWithWrongRoleThenAccessDenied() {
- assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(
- () -> this.bankService.readAccount("12345678"));
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @WithMockUser(roles="ADMIN")
- @Test
- fun readAccountWithAdminRoleThenInvokes() {
- val account: Account = this.bankService.readAccount("12345678")
- // ... assertions
- }
- @WithMockUser(roles="WRONG")
- @Test
- fun readAccountWithWrongRoleThenAccessDenied() {
- assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy {
- this.bankService.readAccount("12345678")
- }
- }
- ----
- ======
- [TIP]
- `@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>>.
- 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>>.
- 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`].
- 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`.
- If not, Spring Security will throw an `AccessDeniedException` and return a 403 status code.
- [[use-postauthorize]]
- === Authorization Method Results with `@PostAuthorize`
- 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:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- public class BankService {
- @PostAuthorize("returnObject.owner == authentication.name")
- public Account readAccount(Long id) {
- // ... is only returned if the `Account` belongs to the logged in user
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Component
- open class BankService {
- @PostAuthorize("returnObject.owner == authentication.name")
- fun readAccount(val id: Long): Account {
- // ... is only returned if the `Account` belongs to the logged in user
- }
- }
- ----
- ======
- This is meant to indicate that the method can only return the value if the provided expression `returnObject.owner == authentication.name` passes.
- `returnObject` represents the `Account` object to be returned.
- You can then xref:servlet/test/method.adoc[test the class] to confirm it is enforcing the authorization rule:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Autowired
- BankService bankService;
- @WithMockUser(username="owner")
- @Test
- void readAccountWhenOwnedThenReturns() {
- Account account = this.bankService.readAccount("12345678");
- // ... assertions
- }
- @WithMockUser(username="wrong")
- @Test
- void readAccountWhenNotOwnedThenAccessDenied() {
- assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(
- () -> this.bankService.readAccount("12345678"));
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @WithMockUser(username="owner")
- @Test
- fun readAccountWhenOwnedThenReturns() {
- val account: Account = this.bankService.readAccount("12345678")
- // ... assertions
- }
- @WithMockUser(username="wrong")
- @Test
- fun readAccountWhenNotOwnedThenAccessDenied() {
- assertThatExceptionOfType(AccessDeniedException::class.java).isThrownBy {
- this.bankService.readAccount("12345678")
- }
- }
- ----
- ======
- [TIP]
- `@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>>.
- `@PostAuthorize` is particularly helpful when defending against https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html[Insecure Direct Object Reference].
- In fact, it can be defined as a <<meta-annotations,meta-annotation>> like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Target({ ElementType.METHOD, ElementType.TYPE })
- @Retention(RetentionPolicy.RUNTIME)
- @PostAuthorize("returnObject.owner == authentication.name")
- public @interface RequireOwnership {}
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Target(ElementType.METHOD, ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @PostAuthorize("returnObject.owner == authentication.name")
- annotation class RequireOwnership
- ----
- ======
- Allowing you to instead annotate the service in the following way:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- public class BankService {
- @RequireOwnership
- public Account readAccount(Long id) {
- // ... is only returned if the `Account` belongs to the logged in user
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Component
- open class BankService {
- @RequireOwnership
- fun readAccount(val id: Long): Account {
- // ... is only returned if the `Account` belongs to the logged in user
- }
- }
- ----
- ======
- The result is that the above method will only return the `Account` if its `owner` attribute matches the logged-in user's `name`.
- If not, Spring Security will throw an `AccessDeniedException` and return a 403 status code.
- [[use-prefilter]]
- === Filtering Method Parameters with `@PreFilter`
- [NOTE]
- `@PreFilter` is not yet supported for Kotlin-specific data types; for that reason, only Java snippets are shown
- 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:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- public class BankService {
- @PreFilter("filterObject.owner == authentication.name")
- public Collection<Account> updateAccounts(Account... accounts) {
- // ... `accounts` will only contain the accounts owned by the logged-in user
- return updated;
- }
- }
- ----
- ======
- This is meant to filter out any values from `accounts` where the expression `filterObject.owner == authentication.name` fails.
- `filterObject` represents each `account` in `accounts` and is used to test each `account`.
- You can then test the class in the following way to confirm it is enforcing the authorization rule:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Autowired
- BankService bankService;
- @WithMockUser(username="owner")
- @Test
- void updateAccountsWhenOwnedThenReturns() {
- Account ownedBy = ...
- Account notOwnedBy = ...
- Collection<Account> updated = this.bankService.updateAccounts(ownedBy, notOwnedBy);
- assertThat(updated).containsOnly(ownedBy);
- }
- ----
- ======
- [TIP]
- `@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>>.
- `@PreFilter` supports arrays, collections, maps, and streams (so long as the stream is still open).
- For example, the above `updateAccounts` declaration will function the same way as the following other four:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @PreFilter("filterObject.owner == authentication.name")
- public Collection<Account> updateAccounts(Account[] accounts)
- @PreFilter("filterObject.owner == authentication.name")
- public Collection<Account> updateAccounts(Collection<Account> accounts)
- @PreFilter("filterObject.value.owner == authentication.name")
- public Collection<Account> updateAccounts(Map<String, Account> accounts)
- @PreFilter("filterObject.owner == authentication.name")
- public Collection<Account> updateAccounts(Stream<Account> accounts)
- ----
- ======
- The result is that the above method will only have the `Account` instances where their `owner` attribute matches the logged-in user's `name`.
- [[use-postfilter]]
- === Filtering Method Results with `@PostFilter`
- [NOTE]
- `@PostFilter` is not yet supported for Kotlin-specific data types; for that reason, only Java snippets are shown
- 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:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- public class BankService {
- @PostFilter("filterObject.owner == authentication.name")
- public Collection<Account> readAccounts(String... ids) {
- // ... the return value will be filtered to only contain the accounts owned by the logged-in user
- return accounts;
- }
- }
- ----
- ======
- This is meant to filter out any values from the return value where the expression `filterObject.owner == authentication.name` fails.
- `filterObject` represents each `account` in `accounts` and is used to test each `account`.
- You can then test the class like so to confirm it is enforcing the authorization rule:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Autowired
- BankService bankService;
- @WithMockUser(username="owner")
- @Test
- void readAccountsWhenOwnedThenReturns() {
- Collection<Account> accounts = this.bankService.updateAccounts("owner", "not-owner");
- assertThat(accounts).hasSize(1);
- assertThat(accounts.get(0).getOwner()).isEqualTo("owner");
- }
- ----
- ======
- [TIP]
- `@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>>.
- `@PostFilter` supports arrays, collections, maps, and streams (so long as the stream is still open).
- For example, the above `readAccounts` declaration will function the same way as the following other three:
- ```java
- @PostFilter("filterObject.owner == authentication.name")
- public Account[] readAccounts(String... ids)
- @PostFilter("filterObject.value.owner == authentication.name")
- public Map<String, Account> readAccounts(String... ids)
- @PostFilter("filterObject.owner == authentication.name")
- public Stream<Account> readAccounts(String... ids)
- ```
- The result is that the above method will return the `Account` instances where their `owner` attribute matches the logged-in user's `name`.
- [NOTE]
- 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.
- [[use-secured]]
- === Authorizing Method Invocation with `@Secured`
- {security-api-url}org/springframework/security/access/annotation/Secured.html[`@Secured`] is a legacy option for authorizing invocations.
- <<use-preauthorize,`@PreAuthorize`>> supercedes it and is recommended instead.
- To use the `@Secured` annotation, you should first change your Method Security declaration to enable it like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @EnableMethodSecurity(securedEnabled = true)
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @EnableMethodSecurity(securedEnabled = true)
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <sec:method-security secured-enabled="true"/>
- ----
- ======
- This will cause Spring Security to publish <<annotation-method-interceptors,the corresponding method interceptor>> that authorizes methods, classes, and interfaces annotated with `@Secured`.
- [[use-jsr250]]
- === Authorizing Method Invocation with JSR-250 Annotations
- In case you would like to use https://jcp.org/en/jsr/detail?id=250[JSR-250] annotations, Spring Security also supports that.
- <<use-preauthorize,`@PreAuthorize`>> has more expressive power and is thus recommended.
- To use the JSR-250 annotations, you should first change your Method Security declaration to enable them like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @EnableMethodSecurity(jsr250Enabled = true)
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @EnableMethodSecurity(jsr250Enabled = true)
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <sec:method-security jsr250-enabled="true"/>
- ----
- ======
- 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`.
- [[class-or-interface-annotations]]
- === Declaring Annotations at the Class or Interface Level
- It's also supported to have Method Security annotations at the class and interface level.
- If it is at the class level like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Controller
- @PreAuthorize("hasAuthority('ROLE_USER')")
- public class MyController {
- @GetMapping("/endpoint")
- public String endpoint() { ... }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Controller
- @PreAuthorize("hasAuthority('ROLE_USER')")
- open class MyController {
- @GetMapping("/endpoint")
- fun endpoint(): String { ... }
- }
- ----
- ======
- then all methods inherit the class-level behavior.
- Or, if it's declared like the following at both the class and method level:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Controller
- @PreAuthorize("hasAuthority('ROLE_USER')")
- public class MyController {
- @GetMapping("/endpoint")
- @PreAuthorize("hasAuthority('ROLE_ADMIN')")
- public String endpoint() { ... }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Controller
- @PreAuthorize("hasAuthority('ROLE_USER')")
- open class MyController {
- @GetMapping("/endpoint")
- @PreAuthorize("hasAuthority('ROLE_ADMIN')")
- fun endpoint(): String { ... }
- }
- ----
- ======
- then methods declaring the annotation override the class-level annotation.
- The same is true for interfaces, with the exception that if a class inherits the annotation from two different interfaces, then startup will fail.
- This is because Spring Security has no way to tell which one you want to use.
- In cases like this, you can resolve the ambiguity by adding the annotation to the concrete method.
- [[meta-annotations]]
- === Using Meta Annotations
- Method Security supports meta annotations.
- This means that you can take any annotation and improve readability based on your application-specific use cases.
- For example, you can simplify `@PreAuthorize("hasRole('ADMIN')")` to `@IsAdmin` like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Target({ ElementType.METHOD, ElementType.TYPE })
- @Retention(RetentionPolicy.RUNTIME)
- @PreAuthorize("hasRole('ADMIN')")
- public @interface IsAdmin {}
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Target(ElementType.METHOD, ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @PreAuthorize("hasRole('ADMIN')")
- annotation class IsAdmin
- ----
- ======
- And the result is that on your secured methods you can now do the following instead:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- public class BankService {
- @IsAdmin
- public Account readAccount(Long id) {
- // ... is only returned if the `Account` belongs to the logged in user
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Component
- open class BankService {
- @IsAdmin
- fun readAccount(val id: Long): Account {
- // ... is only returned if the `Account` belongs to the logged in user
- }
- }
- ----
- ======
- This results in more readable method definitions.
- [[enable-annotation]]
- === Enabling Certain Annotations
- You can turn off ``@EnableMethodSecurity``'s pre-configuration and replace it with you own.
- You may choose to do this if you want to <<custom-authorization-managers,customize the `AuthorizationManager`>> or `Pointcut`.
- Or you may simply want to only enable a specific annotation, like `@PostAuthorize`.
- You can do this in the following way:
- .Only @PostAuthorize Configuration
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableMethodSecurity(prePostEnabled = false)
- class MethodSecurityConfig {
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- Advisor postAuthorize() {
- return AuthorizationManagerBeforeMethodInterceptor.postAuthorize();
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- @EnableMethodSecurity(prePostEnabled = false)
- class MethodSecurityConfig {
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- fun postAuthorize() : Advisor {
- return AuthorizationManagerBeforeMethodInterceptor.postAuthorize()
- }
- }
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <sec:method-security pre-post-enabled="false"/>
- <aop:config/>
- <bean id="postAuthorize"
- class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
- factory-method="postAuthorize"/>
- ----
- ======
- The above snippet achieves this by first disabling Method Security's pre-configurations and then publishing <<annotation-method-interceptors, the `@PostAuthorize` interceptor>> itself.
- [[use-intercept-methods]]
- == Authorizing with `<intercept-methods>`
- 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.
- 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:
- [tabs]
- ======
- Xml::
- +
- [source,xml,role="primary"]
- ----
- <bean class="org.mycompany.MyController">
- <intercept-methods>
- <protect method="get*" access="hasAuthority('read')"/>
- <protect method="*" access="hasAuthority('write')"/>
- </intercept-methods>
- </bean>
- ----
- ======
- [NOTE]
- This only supports matching method by prefix or by name.
- If your needs are more complex than that, <<authorizing-with-annotations,use annotation support>> instead.
- [[use-programmatic-authorization]]
- == Authorizing Methods Programmatically
- As you've already seen, there are several ways that you can specify non-trivial authorization rules using <<authorization-expressions, Method Security SpEL expressions>>.
- There are a number of ways that you can instead allow your logic to be Java-based instead of SpEL-based.
- This gives use access the entire Java language for increased testability and flow control.
- === Using a Custom Bean in SpEL
- The first way to authorize a method programmatically is a two-step process.
- First, declare a bean that has a method that takes a `MethodSecurityExpressionOperations` instance like the following:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component("authz")
- public class AuthorizationLogic {
- public boolean decide(MethodSecurityExpressionOperations operations) {
- // ... authorization logic
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Component("authz")
- open class AuthorizationLogic {
- fun decide(val operations: MethodSecurityExpressionOperations): boolean {
- // ... authorization logic
- }
- }
- ----
- ======
- Then, reference that bean in your annotations in the following way:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Controller
- public class MyController {
- @PreAuthorize("@authz.decide(#root)")
- @GetMapping("/endpoint")
- public String endpoint() {
- // ...
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Controller
- open class MyController {
- @PreAuthorize("@authz.decide(#root)")
- @GetMapping("/endpoint")
- fun String endpoint() {
- // ...
- }
- }
- ----
- ======
- Spring Security will invoke the given method on that bean for each method invocation.
- 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.
- It also has access to the full Java language.
- [[custom-authorization-managers]]
- === Using a Custom Authorization Manager
- The second way to authorize a method programmatically is to create a custom xref:servlet/authorization/architecture.adoc#_the_authorizationmanager[`AuthorizationManager`].
- First, declare an authorization manager instance, perhaps like this one:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- public class MyAuthorizationManager implements AuthorizationManager<MethodInvocation>, AuthorizationManager<MethodInvocationResult> {
- @Override
- public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
- // ... authorization logic
- }
- @Override
- public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult invocation) {
- // ... authorization logic
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Component
- class MyAuthorizationManager : AuthorizationManager<MethodInvocation>, AuthorizationManager<MethodInvocationResult> {
- override fun check(authentication: Supplier<Authentication>, invocation: MethodInvocation): AuthorizationDecision {
- // ... authorization logic
- }
- override fun check(authentication: Supplier<Authentication>, invocation: MethodInvocationResult): AuthorizationDecision {
- // ... authorization logic
- }
- }
- ----
- ======
- Then, publish the method interceptor with a pointcut that corresponds to when you want that `AuthorizationManager` to run.
- For example, you could replace how `@PreAuthorize` and `@PostAuthorize` work like so:
- .Only @PreAuthorize and @PostAuthorize Configuration
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Configuration
- @EnableMethodSecurity(prePostEnabled = false)
- class MethodSecurityConfig {
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- Advisor preAuthorize(MyAuthorizationManager manager) {
- return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
- }
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- Advisor postAuthorize(MyAuthorizationManager manager) {
- return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager);
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Configuration
- @EnableMethodSecurity(prePostEnabled = false)
- class MethodSecurityConfig {
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- fun preAuthorize(val manager: MyAuthorizationManager) : Advisor {
- return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager)
- }
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- fun postAuthorize(val manager: MyAuthorizationManager) : Advisor {
- return AuthorizationManagerAfterMethodInterceptor.postAuthorize(manager)
- }
- }
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <sec:method-security pre-post-enabled="false"/>
- <aop:config/>
- <bean id="preAuthorize"
- class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
- factory-method="preAuthorize">
- <constructor-arg ref="myAuthorizationManager"/>
- </bean>
- <bean id="postAuthorize"
- class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor"
- factory-method="postAuthorize">
- <constructor-arg ref="myAuthorizationManager"/>
- </bean>
- ----
- ======
- [TIP]
- ====
- You can place your interceptor in between Spring Security method interceptors using the order constants specified in `AuthorizationInterceptorsOrder`.
- ====
- [[customizing-expression-handling]]
- === Customizing Expression Handling
- Or, third, you can customize how each SpEL expression is handled.
- To do that, you can expose a custom {security-api-url}org.springframework.security.access.expression.method.MethodSecurityExpressionHandler.html[`MethodSecurityExpressionHandler`], like so:
- .Custom MethodSecurityExpressionHandler
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Bean
- static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
- DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
- handler.setRoleHierarchy(roleHierarchy);
- return handler;
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- companion object {
- @Bean
- fun methodSecurityExpressionHandler(val roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler {
- val handler = DefaultMethodSecurityExpressionHandler();
- handler.setRoleHierarchy(roleHierarchy);
- return handler;
- }
- }
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <sec:method-security>
- <sec:expression-handler ref="myExpressionHandler"/>
- </sec:method-security>
- <bean id="myExpressionHandler"
- class="org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler">
- <property name="roleHierarchy" ref="roleHierarchy"/>
- </bean>
- ----
- ======
- [TIP]
- ====
- We expose `MethodSecurityExpressionHandler` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
- ====
- You can also <<subclass-defaultmethodsecurityexpressionhandler,subclass `DefaultMessageSecurityExpressionHandler`>> to add your own custom authorization expressions beyond the defaults.
- [[use-aspectj]]
- == Authorizing with AspectJ
- [[match-by-pointcut]]
- === Matching Methods with Custom Pointcuts
- 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].
- This has the potential advantage of centralizing method-level authorization rules.
- 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:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- static Advisor protectServicePointcut() {
- JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
- pattern.setPattern("execution(* com.mycompany.*Service.*(..))");
- return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"));
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
- companion object {
- @Bean
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
- fun protectServicePointcut(): Advisor {
- var pattern = JdkRegexpMethodPointcut();
- pattern.setPattern("execution(* com.mycompany.*Service.*(..))");
- return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"));
- }
- }
- ----
- ======
- [source,xml]
- ----
- <sec:method-security>
- <protect-pointcut expression="execution(* com.mycompany.*Service.*(..))" access="hasRole('USER')"/>
- </sec:method-security>
- ----
- [[weave-aspectj]]
- === Integrate with AspectJ Byte-weaving
- Performance can at times be enhanced by using AspectJ to weave Spring Security advice into the byte code of your beans.
- After setting up AspectJ, you can quite simply state in the `@EnableMethodSecurity` annotation or `<method-security>` element that you are using AspectJ:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @EnableMethodSecurity(mode=AdviceMode.ASPECTJ)
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @EnableMethodSecurity(mode=AdviceMode.ASPECTJ)
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <sec:method-security mode="aspectj"/>
- ----
- ======
- And the result will be that Spring Security will publish its advisors as AspectJ advice so that they can be woven in accordingly.
- [[changing-the-order]]
- == Specifying Order
- 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.
- Namely, the `@PreFilter` method interceptor's order is 100, ``@PreAuthorize``'s is 200, and so on.
- 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`.
- In other words, they are located at the end of the advisor chain by default.
- At times, it can be valuable to have other advice execute before Spring Security.
- 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.
- To get `@EnableTransactionManagement` to open a transaction before method authorization advice runs, you can set ``@EnableTransactionManagement``'s order like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @EnableTransactionManagement(order = 0)
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @EnableTransactionManagement(order = 0)
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <tx:annotation-driven ref="txManager" order="0"/>
- ----
- ======
- 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.
- [[authorization-expressions]]
- == Expressing Authorization with SpEL
- You've already seen several examples using SpEL, so now let's cover the API a bit more in depth.
- Spring Security encapsulates all of its authorization fields and methods in a set of root objects.
- The most generic root object is called `SecurityExpressionRoot` and it forms the basis for `MethodSecurityExpressionRoot`.
- Spring Security supplies this root object to `MethodSecurityEvaluationContext` when preparing to evaluate an authorization expression.
- [[using-authorization-expression-fields-and-methods]]
- === Using Authorization Expression Fields and Methods
- The first thing this provides is an enhanced set of authorization fields and methods to your SpEL expressions.
- What follows is a quick overview of the most common methods:
- * `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
- * `denyAll` - The method is not allowed under any circumstances; note that in this case, the `Authentication` is never retrieved from the session
- * `hasAuthority` - The method requires that the `Authentication` have xref:servlet/authorization/architecture.adoc#authz-authorities[a `GrantedAuthority`] that matches the given value
- * `hasRole` - A shortcut for `hasAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
- * `hasAnyAuthority` - The method requires that the `Authentication` have a `GrantedAuthority` that matches any of the given values
- * `hasAnyRole` - A shortcut for `hasAnyAuthority` that prefixes `ROLE_` or whatever is configured as the default prefix
- * `hasPermission` - A hook into your `PermissionEvaluator` instance for doing object-level authorization
- And here is a brief look at the most common fields:
- * `authentication` - The `Authentication` instance associated with this method invocation
- * `principal` - The `Authentication#getPrincipal` associated with this method invocation
- 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:
- .Authorize Requests
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- public class MyService {
- @PreAuthorize("denyAll") <1>
- MyResource myDeprecatedMethod(...);
- @PreAuthorize("hasRole('ADMIN')") <2>
- MyResource writeResource(...)
- @PreAuthorize("hasAuthority('db') and hasRole('ADMIN')") <3>
- MyResource deleteResource(...)
- @PreAuthorize("principal.claims['aud'] == 'my-audience'") <4>
- MyResource readResource(...);
- @PreAuthorize("@authz.check(authentication, #root)")
- MyResource shareResource(...);
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Component
- open class MyService {
- @PreAuthorize("denyAll") <1>
- fun myDeprecatedMethod(...): MyResource
- @PreAuthorize("hasRole('ADMIN')") <2>
- fun writeResource(...): MyResource
- @PreAuthorize("hasAuthority('db') and hasRole('ADMIN')") <3>
- fun deleteResource(...): MyResource
- @PreAuthorize("principal.claims['aud'] == 'my-audience'") <4>
- fun readResource(...): MyResource
- @PreAuthorize("@authz.check(#root)")
- fun shareResource(...): MyResource;
- }
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <sec:method-security>
- <protect-pointcut expression="execution(* com.mycompany.*Service.myDeprecatedMethod(..))" access="denyAll"/> <1>
- <protect-pointcut expression="execution(* com.mycompany.*Service.writeResource(..))" access="hasRole('ADMIN')"/> <2>
- <protect-pointcut expression="execution(* com.mycompany.*Service.deleteResource(..))" access="hasAuthority('db') and hasRole('ADMIN')"/> <3>
- <protect-pointcut expression="execution(* com.mycompany.*Service.readResource(..))" access="principal.claims['aud'] == 'my-audience'"/> <4>
- <protect-pointcut expression="execution(* com.mycompany.*Service.shareResource(..))" access="@authz.check(#root)"/> <5>
- </sec:method-security>
- ----
- ======
- <1> This method may not be invoked by anyone for any reason
- <2> This method may only be invoked by ``Authentication``s granted the `ROLE_ADMIN` authority
- <3> This method may only be invoked by ``Authentication``s granted the `db` and `ROLE_ADMIN` authorities
- <4> This method may only be invoked by ``Princpal``s with an `aud` claim equal to "my-audience"
- <5> This method may only be invoked if the bean ``authz``'s `check` method returns `true`
- [[using_method_parameters]]
- === Using Method Parameters
- Additionally, Spring Security provides a mechanism for discovering method parameters so they can also be accessed in the SpEL expression as well.
- For a complete reference, Spring Security uses `DefaultSecurityParameterNameDiscoverer` to discover the parameter names.
- By default, the following options are tried for a method.
- 1. If Spring Security's `@P` annotation is present on a single argument to the method, the value is used.
- The following example uses the `@P` annotation:
- +
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- import org.springframework.security.access.method.P;
- ...
- @PreAuthorize("hasPermission(#c, 'write')")
- public void updateContact(@P("c") Contact contact);
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- import org.springframework.security.access.method.P
- ...
- @PreAuthorize("hasPermission(#c, 'write')")
- fun doSomething(@P("c") contact: Contact?)
- ----
- ======
- +
- The intention of this expression is to require that the current `Authentication` have `write` permission specifically for this `Contact` instance.
- +
- Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation.
- * 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.
- The following example uses the `@Param` annotation:
- +
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- import org.springframework.data.repository.query.Param;
- ...
- @PreAuthorize("#n == authentication.name")
- Contact findContactByName(@Param("n") String name);
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- import org.springframework.data.repository.query.Param
- ...
- @PreAuthorize("#n == authentication.name")
- fun findContactByName(@Param("n") name: String?): Contact?
- ----
- ======
- +
- The intention of this expression is to require that `name` be equal to `Authentication#getName` for the invocation to be authorized.
- +
- Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation.
- * If you compile your code with the `-parameters` argument, the standard JDK reflection API is used to discover the parameter names.
- This works on both classes and interfaces.
- * Finally, if you compile your code with debug symbols, the parameter names are discovered by using the debug symbols.
- This does not work for interfaces, since they do not have debug information about the parameter names.
- For interfaces, either annotations or the `-parameters` approach must be used.
- [[migration-enableglobalmethodsecurity]]
- == Migrating from `@EnableGlobalMethodSecurity`
- If you are using `@EnableGlobalMethodSecurity`, you should migrate to `@EnableMethodSecurity`.
- [[servlet-replace-globalmethodsecurity-with-methodsecurity]]
- === 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]
- {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.
- 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.
- This means that the following two listings are functionally equivalent:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <global-method-security pre-post-enabled="true"/>
- ----
- ======
- and:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @EnableMethodSecurity
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @EnableMethodSecurity
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <method-security/>
- ----
- ======
- For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior.
- For example, a listing like:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @EnableGlobalMethodSecurity(securedEnabled = true)
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @EnableGlobalMethodSecurity(securedEnabled = true)
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <global-method-security secured-enabled="true"/>
- ----
- ======
- should change to:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false)
- ----
- Xml::
- +
- [source,xml,role="secondary"]
- ----
- <method-security secured-enabled="true" pre-post-enabled="false"/>
- ----
- ======
- === Use a Custom `@Bean` instead of subclassing `DefaultMethodSecurityExpressionHandler`
- As a performance optimization, a new method was introduced to `MethodSecurityExpressionHandler` that takes a `Supplier<Authentication>` instead of an `Authentication`.
- This allows Spring Security to defer the lookup of the `Authentication`, and is taken advantage of automatically when you use `@EnableMethodSecurity` instead of `@EnableGlobalMethodSecurity`.
- However, let's say that your code extends `DefaultMethodSecurityExpressionHandler` and overrides `createSecurityExpressionRoot(Authentication, MethodInvocation)` to return a custom `SecurityExpressionRoot` instance.
- This will no longer work because the arrangement that `@EnableMethodSecurity` sets up calls `createEvaluationContext(Supplier<Authentication>, MethodInvocation)` instead.
- Happily, such a level of customization is often unnecessary.
- Instead, you can create a custom bean with the authorization methods that you need.
- For example, let's say you are wanting a custom evaluation of `@PostAuthorize("hasAuthority('ADMIN')")`.
- You can create a custom `@Bean` like this one:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- class MyAuthorizer {
- boolean isAdmin(MethodSecurityExpressionOperations root) {
- boolean decision = root.hasAuthority("ADMIN");
- // custom work ...
- return decision;
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- class MyAuthorizer {
- fun isAdmin(val root: MethodSecurityExpressionOperations): boolean {
- val decision = root.hasAuthority("ADMIN");
- // custom work ...
- return decision;
- }
- }
- ----
- ======
- and then refer to it in the annotation like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @PreAuthorize("@authz.isAdmin(#root)")
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @PreAuthorize("@authz.isAdmin(#root)")
- ----
- ======
- [[subclass-defaultmethodsecurityexpressionhandler]]
- ==== I'd still prefer to subclass `DefaultMethodSecurityExpressionHandler`
- If you must continue subclassing `DefaultMethodSecurityExpressionHandler`, you can still do so.
- Instead, override the `createEvaluationContext(Supplier<Authentication>, MethodInvocation)` method like so:
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Component
- class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
- @Override
- public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
- StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
- MethodSecurityExpressionOperations delegate = (MethodSecurityExpressionOperations) context.getRootObject().getValue();
- MySecurityExpressionRoot root = new MySecurityExpressionRoot(delegate);
- context.setRootObject(root);
- return context;
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Component
- class MyExpressionHandler: DefaultMethodSecurityExpressionHandler {
- override fun createEvaluationContext(val authentication: Supplier<Authentication>,
- val mi: MethodInvocation): EvaluationContext {
- val context = super.createEvaluationContext(authentication, mi) as StandardEvaluationContext
- val delegate = context.getRootObject().getValue() as MethodSecurityExpressionOperations
- val root = MySecurityExpressionRoot(delegate)
- context.setRootObject(root);
- return context;
- }
- }
- ----
- ======
- == Further Reading
- 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.
- 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].
|