For the latest stable version, please use Spring Security 6.2.4!

Method Security

From version 2.0 onwards Spring Security has improved support substantially for adding security to your service layer methods. It provides support for JSR-250 annotation security as well as the framework’s original @Secured annotation. From 3.0 you can also make use of new expression-based annotations. You can apply security to a single bean, using the intercept-methods element to decorate the bean declaration, or you can secure multiple beans across the entire service layer using the AspectJ style pointcuts.

EnableMethodSecurity

In Spring Security 5.6, we can enable annotation-based security using the @EnableMethodSecurity annotation on any @Configuration instance.

This improves upon @EnableGlobalMethodSecurity in a number of ways. @EnableMethodSecurity:

  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

For earlier versions, please read about similar support with @EnableGlobalMethodSecurity.

For example, the following would enable Spring Security’s @PreAuthorize annotation:

Method Security Configuration
  • Java

  • Kotlin

  • Xml

@EnableMethodSecurity
public class MethodSecurityConfig {
	// ...
}
@EnableMethodSecurity
class MethodSecurityConfig {
	// ...
}
<sec:method-security/>

Adding an annotation to a method (on a class or interface) would then limit the access to that method accordingly. Spring Security’s native annotation support defines a set of attributes for the method. These will be passed to the DefaultAuthorizationMethodInterceptorChain for it to make the actual decision:

Method Security Annotation Usage
  • Java

  • Kotlin

public interface BankService {
	@PreAuthorize("hasRole('USER')")
	Account readAccount(Long id);

	@PreAuthorize("hasRole('USER')")
	List<Account> findAccounts();

	@PreAuthorize("hasRole('TELLER')")
	Account post(Account account, Double amount);
}
interface BankService {
	@PreAuthorize("hasRole('USER')")
	fun readAccount(id : Long) : Account

	@PreAuthorize("hasRole('USER')")
	fun findAccounts() : List<Account>

	@PreAuthorize("hasRole('TELLER')")
	fun post(account : Account, amount : Double) : Account
}

You can enable support for Spring Security’s @Secured annotation using:

@Secured Configuration
  • Java

  • Kotlin

  • Xml

@EnableMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
	// ...
}
@EnableMethodSecurity(securedEnabled = true)
class MethodSecurityConfig {
	// ...
}
<sec:method-security secured-enabled="true"/>

or JSR-250 using:

JSR-250 Configuration
  • Java

  • Kotlin

  • Xml

@EnableMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
	// ...
}
@EnableMethodSecurity(jsr250Enabled = true)
class MethodSecurityConfig {
	// ...
}
<sec:method-security jsr250-enabled="true"/>

Customizing Authorization

Spring Security’s @PreAuthorize, @PostAuthorize, @PreFilter, and @PostFilter ship with rich expression-based support.

If you need to customize the way that expressions are handled, you can expose a custom MethodSecurityExpressionHandler, like so:

Custom MethodSecurityExpressionHandler
  • Java

  • Kotlin

  • Xml

@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
	DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
	handler.setTrustResolver(myCustomTrustResolver);
	return handler;
}
companion object {
	@Bean
	fun methodSecurityExpressionHandler() : MethodSecurityExpressionHandler {
		val handler = DefaultMethodSecurityExpressionHandler();
		handler.setTrustResolver(myCustomTrustResolver);
		return handler;
	}
}
<sec:method-security>
	<sec:expression-handler ref="myExpressionHandler"/>
</sec:method-security>

<bean id="myExpressionHandler"
		class="org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler">
	<property name="trustResolver" ref="myCustomTrustResolver"/>
</bean>

We expose MethodSecurityExpressionHandler using a static method to ensure that Spring publishes it before it initializes Spring Security’s method security @Configuration classes

Also, for role-based authorization, Spring Security adds a default ROLE_ prefix, which is uses when evaluating expressions like hasRole.

You can configure the authorization rules to use a different prefix by exposing a GrantedAuthorityDefaults bean, like so:

Custom MethodSecurityExpressionHandler
  • Java

  • Kotlin

  • Xml

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("MYPREFIX_");
}
companion object {
	@Bean
	fun grantedAuthorityDefaults() : GrantedAuthorityDefaults {
		return GrantedAuthorityDefaults("MYPREFIX_");
	}
}
<sec:method-security/>

<bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
	<constructor-arg value="MYPREFIX_"/>
</bean>

We expose GrantedAuthorityDefaults using a static method to ensure that Spring publishes it before it initializes Spring Security’s method security @Configuration classes

Custom Authorization Managers

Method authorization is a combination of before- and after-method authorization.

Before-method authorization is performed before the method is invoked. If that authorization denies access, the method is not invoked, and an AccessDeniedException is thrown. After-method authorization is performed after the method is invoked, but before the method returns to the caller. If that authorization denies access, the value is not returned, and an AccessDeniedException is thrown

To recreate what adding @EnableMethodSecurity does by default, you would publish the following configuration:

Full Pre-post Method Security Configuration
  • Java

  • Kotlin

  • Xml

@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor preFilterAuthorizationMethodInterceptor() {
		return new PreFilterAuthorizationMethodInterceptor();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor preAuthorizeAuthorizationMethodInterceptor() {
		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor postAuthorizeAuthorizationMethodInterceptor() {
		return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor postFilterAuthorizationMethodInterceptor() {
		return new PostFilterAuthorizationMethodInterceptor();
	}
}
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	fun preFilterAuthorizationMethodInterceptor() : Advisor {
		return PreFilterAuthorizationMethodInterceptor();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	fun preAuthorizeAuthorizationMethodInterceptor() : Advisor {
		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	fun postAuthorizeAuthorizationMethodInterceptor() : Advisor {
		return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	fun postFilterAuthorizationMethodInterceptor() : Advisor {
		return PostFilterAuthorizationMethodInterceptor();
	}
}
<sec:method-security pre-post-enabled="false"/>

<aop:config/>

<bean id="preFilterAuthorizationMethodInterceptor"
		class="org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor"/>
<bean id="preAuthorizeAuthorizationMethodInterceptor"
		class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
		factory-method="preAuthorize"/>
<bean id="postAuthorizeAuthorizationMethodInterceptor"
		class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor"
		factory-method="postAuthorize"/>
<bean id="postFilterAuthorizationMethodInterceptor"
		class="org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor"/>

Notice that Spring Security’s method security is built using Spring AOP. So, interceptors are invoked based on the order specified. This can be customized by calling setOrder on the interceptor instances like so:

Publish Custom Advisor
  • Java

  • Kotlin

  • Xml

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postFilterAuthorizationMethodInterceptor() {
	PostFilterAuthorizationMethodInterceptor interceptor = new PostFilterAuthorizationMethodInterceptor();
	interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
	return interceptor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
fun postFilterAuthorizationMethodInterceptor() : Advisor {
	val interceptor = PostFilterAuthorizationMethodInterceptor();
	interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
	return interceptor;
}
<bean id="postFilterAuthorizationMethodInterceptor"
		class="org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor">
	<property name="order"
			value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).POST_AUTHORIZE.getOrder() -1}"/>
</bean>

You may want to only support @PreAuthorize in your application, in which case you can do the following:

Only @PreAuthorize Configuration
  • Java

  • Kotlin

  • Xml

@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor preAuthorize() {
		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
	}
}
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	fun preAuthorize() : Advisor {
		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize()
	}
}
<sec:method-security pre-post-enabled="false"/>

<aop:config/>

<bean id="preAuthorizeAuthorizationMethodInterceptor"
	class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
	factory-method="preAuthorize"/>

Or, you may have a custom before-method AuthorizationManager that you want to add to the list.

In this case, you will need to tell Spring Security both the AuthorizationManager and to which methods and classes your authorization manager applies.

Thus, you can configure Spring Security to invoke your AuthorizationManager in between @PreAuthorize and @PostAuthorize like so:

Custom Before Advisor
  • Java

  • Kotlin

  • Xml

@EnableMethodSecurity
class MethodSecurityConfig {
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public Advisor customAuthorize() {
		JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
		pattern.setPattern("org.mycompany.myapp.service.*");
		AuthorizationManager<MethodInvocation> rule = AuthorityAuthorizationManager.isAuthenticated();
		AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(pattern, rule);
		interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
		return interceptor;
    }
}
@EnableMethodSecurity
class MethodSecurityConfig {
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	fun customAuthorize() : Advisor {
		val pattern = JdkRegexpMethodPointcut();
		pattern.setPattern("org.mycompany.myapp.service.*");
		val rule = AuthorityAuthorizationManager.isAuthenticated();
		val interceptor = AuthorizationManagerBeforeMethodInterceptor(pattern, rule);
		interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
		return interceptor;
	}
}
<sec:method-security/>

<aop:config/>

<bean id="customAuthorize"
		class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor">
	<constructor-arg>
		<bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
			<property name="pattern" value="org.mycompany.myapp.service.*"/>
		</bean>
	</constructor-arg>
	<constructor-arg>
		<bean class="org.springframework.security.authorization.AuthorityAuthorizationManager"
				factory-method="isAuthenticated"/>
	</constructor-arg>
	<property name="order"
			value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1}"/>
</bean>

You can place your interceptor in between Spring Security method interceptors using the order constants specified in AuthorizationInterceptorsOrder.

The same can be done for after-method authorization. After-method authorization is generally concerned with analysing the return value to verify access.

For example, you might have a method that confirms that the account requested actually belongs to the logged-in user like so:

@PostAuthorize example
  • Java

  • Kotlin

public interface BankService {

	@PreAuthorize("hasRole('USER')")
	@PostAuthorize("returnObject.owner == authentication.name")
	Account readAccount(Long id);
}
interface BankService {

	@PreAuthorize("hasRole('USER')")
	@PostAuthorize("returnObject.owner == authentication.name")
	fun readAccount(id : Long) : Account
}

You can supply your own AuthorizationMethodInterceptor to customize how access to the return value is evaluated.

For example, if you have your own custom annotation, you can configure it like so:

Custom After Advisor
  • Java

  • Kotlin

  • Xml

@EnableMethodSecurity
class MethodSecurityConfig {
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public Advisor customAuthorize(AuthorizationManager<MethodInvocationResult> rules) {
		AnnotationMatchingPointcut pattern = new AnnotationMatchingPointcut(MySecurityAnnotation.class);
		AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(pattern, rules);
		interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
		return interceptor;
	}
}
@EnableMethodSecurity
class MethodSecurityConfig {
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	fun customAuthorize(rules : AuthorizationManager<MethodInvocationResult>) : Advisor {
		val pattern = AnnotationMatchingPointcut(MySecurityAnnotation::class.java);
		val interceptor = AuthorizationManagerAfterMethodInterceptor(pattern, rules);
		interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
		return interceptor;
	}
}
<sec:method-security/>

<aop:config/>

<bean id="customAuthorize"
		class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor">
	<constructor-arg>
		<bean class="org.springframework.aop.support.annotation.AnnotationMethodMatcher">
			<constructor-arg value="#{T(org.mycompany.MySecurityAnnotation)}"/>
		</bean>
	</constructor-arg>
	<constructor-arg>
		<bean class="org.springframework.security.authorization.AuthorityAuthorizationManager"
				factory-method="isAuthenticated"/>
	</constructor-arg>
	<property name="order"
		value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1}"/>
</bean>

and it will be invoked after the @PostAuthorize interceptor.

EnableGlobalMethodSecurity

We can enable annotation-based security using the @EnableGlobalMethodSecurity annotation on any @Configuration instance. For example, the following would enable Spring Security’s @Secured annotation.

  • Java

  • Kotlin

@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
@EnableGlobalMethodSecurity(securedEnabled = true)
open class MethodSecurityConfig {
	// ...
}

Adding an annotation to a method (on a class or interface) would then limit the access to that method accordingly. Spring Security’s native annotation support defines a set of attributes for the method. These will be passed to the AccessDecisionManager for it to make the actual decision:

  • Java

  • Kotlin

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
interface BankService {
    @Secured("IS_AUTHENTICATED_ANONYMOUSLY")
    fun readAccount(id: Long): Account

    @Secured("IS_AUTHENTICATED_ANONYMOUSLY")
    fun findAccounts(): Array<Account>

    @Secured("ROLE_TELLER")
    fun post(account: Account, amount: Double): Account
}

Support for JSR-250 annotations can be enabled using

  • Java

  • Kotlin

@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
// ...
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
open class MethodSecurityConfig {
	// ...
}

These are standards-based and allow simple role-based constraints to be applied but do not have the power Spring Security’s native annotations. To use the new expression-based syntax, you would use

  • Java

  • Kotlin

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
open class MethodSecurityConfig {
	// ...
}

and the equivalent Java code would be

  • Java

  • Kotlin

public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}
interface BankService {
    @PreAuthorize("isAnonymous()")
    fun readAccount(id: Long): Account

    @PreAuthorize("isAnonymous()")
    fun findAccounts(): Array<Account>

    @PreAuthorize("hasAuthority('ROLE_TELLER')")
    fun post(account: Account, amount: Double): Account
}

GlobalMethodSecurityConfiguration

Sometimes you may need to perform operations that are more complicated than are possible with the @EnableGlobalMethodSecurity annotation allow. For these instances, you can extend the GlobalMethodSecurityConfiguration ensuring that the @EnableGlobalMethodSecurity annotation is present on your subclass. For example, if you wanted to provide a custom MethodSecurityExpressionHandler, you could use the following configuration:

  • Java

  • Kotlin

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
	@Override
	protected MethodSecurityExpressionHandler createExpressionHandler() {
		// ... create and return custom MethodSecurityExpressionHandler ...
		return expressionHandler;
	}
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
open class MethodSecurityConfig : GlobalMethodSecurityConfiguration() {
    override fun createExpressionHandler(): MethodSecurityExpressionHandler {
        // ... create and return custom MethodSecurityExpressionHandler ...
        return expressionHandler
    }
}

For additional information about methods that can be overridden, refer to the GlobalMethodSecurityConfiguration Javadoc.

The <global-method-security> Element

This element is used to enable annotation-based security in your application (by setting the appropriate attributes on the element), and also to group together security pointcut declarations which will be applied across your entire application context. You should only declare one <global-method-security> element. The following declaration would enable support for Spring Security’s @Secured:

<global-method-security secured-annotations="enabled" />

Adding an annotation to a method (on an class or interface) would then limit the access to that method accordingly. Spring Security’s native annotation support defines a set of attributes for the method. These will be passed to the AccessDecisionManager for it to make the actual decision:

  • Java

  • Kotlin

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
interface BankService {
    @Secured("IS_AUTHENTICATED_ANONYMOUSLY")
    fun readAccount(id: Long): Account

    @Secured("IS_AUTHENTICATED_ANONYMOUSLY")
    fun findAccounts(): Array<Account>

    @Secured("ROLE_TELLER")
    fun post(account: Account, amount: Double): Account
}

Support for JSR-250 annotations can be enabled using

<global-method-security jsr250-annotations="enabled" />

These are standards-based and allow simple role-based constraints to be applied but do not have the power Spring Security’s native annotations. To use the new expression-based syntax, you would use

<global-method-security pre-post-annotations="enabled" />

and the equivalent Java code would be

  • Java

  • Kotlin

public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}
interface BankService {
    @PreAuthorize("isAnonymous()")
    fun readAccount(id: Long): Account

    @PreAuthorize("isAnonymous()")
    fun findAccounts(): Array<Account>

    @PreAuthorize("hasAuthority('ROLE_TELLER')")
    fun post(account: Account, amount: Double): Account
}

Expression-based annotations are a good choice if you need to define simple rules that go beyond checking the role names against the user’s list of authorities.

The annotated methods will only be secured for instances which are defined as Spring beans (in the same application context in which method-security is enabled). If you want to secure instances which are not created by Spring (using the new operator, for example) then you need to use AspectJ.

You can enable more than one type of annotation in the same application, but only one type should be used for any interface or class as the behaviour will not be well-defined otherwise. If two annotations are found which apply to a particular method, then only one of them will be applied.

Adding Security Pointcuts using protect-pointcut

The use of protect-pointcut is particularly powerful, as it allows you to apply security to many beans with only a simple declaration. Consider the following example:

<global-method-security>
<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))"
	access="ROLE_USER"/>
</global-method-security>

This will protect all methods on beans declared in the application context whose classes are in the com.mycompany package and whose class names end in "Service". Only users with the ROLE_USER role will be able to invoke these methods. As with URL matching, the most specific matches must come first in the list of pointcuts, as the first matching expression will be used. Security annotations take precedence over pointcuts.