mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-31 22:58:58 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1785 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			1785 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 
 | |
| [[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() {
 | |
|     AspectJExpressionPointcut pattern = new AspectJExpressionPointcut()
 | |
|     pattern.setExpression("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 {
 | |
|         val pattern = AspectJExpressionPointcut()
 | |
|         pattern.setExpression("execution(* com.mycompany.*Service.*(..))")
 | |
|         return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"))
 | |
|     }
 | |
| }
 | |
| ----
 | |
| 
 | |
| Xml::
 | |
| +
 | |
| [source,xml,role="secondary"]
 | |
| ----
 | |
| <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].
 |