Spring Security supports method security by using https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context], which is set up by `ReactiveSecurityContextHolder`.
The following example shows how to retrieve the currently logged in user's message:
== EnableReactiveMethodSecurity with AuthorizationManager
In Spring Security 5.8, we can enable annotation-based security using the `@EnableReactiveMethodSecurity(useAuthorizationManager=true)` annotation on any `@Configuration` instance.
This improves upon `@EnableReactiveMethodSecurity` in a number of ways. `@EnableReactiveMethodSecurity(useAuthorizationManager=true)`:
1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters.
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 various method interceptors, like `AuthorizationManagerBeforeReactiveMethodInterceptor`, for it to make the actual decision:
We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes.
Since the `GrantedAuthorityDefaults` bean is part of internal workings of Spring Security, we should also expose it as an infrastructural bean effectively avoiding some warnings related to bean post-processing (see https://github.com/spring-projects/spring-security/issues/14751[gh-14751]).
As you've already seen, there are several ways that you can specify non-trivial authorization rules using xref:servlet/authorization/method-security.adoc#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 decide(MethodSecurityExpressionOperations operations): Mono<Boolean> {
// ... authorization logic
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Component("authz")
open class AuthorizationLogic {
fun decide(val operations: MethodSecurityExpressionOperations): Mono<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 Mono<String> endpoint() {
// ...
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Controller
open class MyController {
@PreAuthorize("@authz.decide(#root)")
@GetMapping("/endpoint")
fun endpoint(): Mono<String> {
// ...
}
}
----
======
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.
[TIP]
In addition to returning a `Mono<Boolean>`, you can also return `Mono.empty()` to indicate that the code abstains from making a decision.
If you want to include more information about the nature of the decision, you can instead return a custom `AuthorizationDecision` like this:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Component("authz")
public class AuthorizationLogic {
public Mono<AuthorizationDecision> decide(MethodSecurityExpressionOperations operations) {
Or throw a custom `AuthorizationDeniedException` instance.
Note, though, that returning an object is preferred as this doesn't incur the expense of generating a stacktrace.
Then, you can access the custom details when you xref:servlet/authorization/method-security.adoc#fallback-values-authorization-denied[customize how the authorization result is handled].
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 MyPreAuthorizeAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
@Override
public Mono<AuthorizationDecision> check(Supplier<Authentication> authentication, MethodInvocation invocation) {
// ... authorization logic
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Component
class MyPreAuthorizeAuthorizationManager : ReactiveAuthorizationManager<MethodInvocation> {
override fun check(authentication: Supplier<Authentication>, invocation: MethodInvocation): Mono<AuthorizationDecision> {
// ... authorization logic
}
}
----
======
Then, publish the method interceptor with a pointcut that corresponds to when you want that `ReactiveAuthorizationManager` to run.
For example, you could replace how `@PreAuthorize` and `@PostAuthorize` work like so:
.Only @PreAuthorize and @PostAuthorize Configuration
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
}
}
----
======
[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 xref:servlet/authorization/method-security.adoc#subclass-defaultmethodsecurityexpressionhandler[`DefaultMessageSecurityExpressionHandler`] to add your own custom authorization expressions beyond the defaults.
Combined with our configuration above, `@PreAuthorize("hasRole('ADMIN')")` ensures that `findByMessage` is invoked only by a user with the `ADMIN` role.
Note that any of the expressions in standard method security work for `@EnableReactiveMethodSecurity`.
However, at this time, we support only a return type of `Boolean` or `boolean` of the expression.
When integrating with xref:reactive/configuration/webflux.adoc#jc-webflux[WebFlux Security], the Reactor Context is automatically established by Spring Security according to the authenticated user: