mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-11-04 08:39:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1783 lines
		
	
	
		
			58 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			1783 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() {
 | 
						|
    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].
 |