| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 | 
							- [[jc-erms]]
 
- = EnableReactiveMethodSecurity
 
- 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:
 
- [NOTE]
 
- ====
 
- For this example to work, the return type of the method must be a `org.reactivestreams.Publisher` (that is, a `Mono` or a `Flux`).
 
- This is necessary to integrate with Reactor's `Context`.
 
- ====
 
- [[jc-enable-reactive-method-security-authorization-manager]]
 
- == 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.
 
- This simplifies reuse and customization.
 
- 2. Supports reactive return types including Kotlin coroutines.
 
- 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
 
- [NOTE]
 
- ====
 
- For earlier versions, please read about similar support with <<jc-enable-reactive-method-security, @EnableReactiveMethodSecurity>>.
 
- ====
 
- For example, the following would enable Spring Security's `@PreAuthorize` annotation:
 
- .Method Security Configuration
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @EnableReactiveMethodSecurity(useAuthorizationManager=true)
 
- public 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 various method interceptors, like `AuthorizationManagerBeforeReactiveMethodInterceptor`, for it to make the actual decision:
 
- .Method Security Annotation Usage
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- public interface BankService {
 
- 	@PreAuthorize("hasRole('USER')")
 
- 	Mono<Account> readAccount(Long id);
 
- 	@PreAuthorize("hasRole('USER')")
 
- 	Flux<Account> findAccounts();
 
- 	@PreAuthorize("@func.apply(#account)")
 
- 	Mono<Account> post(Account account, Double amount);
 
- }
 
- ----
 
- ======
 
- In this case `hasRole` refers to the method found in `SecurityExpressionRoot` that gets invoked by the SpEL evaluation engine.
 
- `@bean` refers to a custom component you have defined, where `apply` can return `Boolean` or `Mono<Boolean>` to indicate the authorization decision.
 
- A bean like that might look something like this:
 
- .Method Security Reactive Boolean Expression
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- public Function<Account, Mono<Boolean>> func() {
 
-     return (account) -> Mono.defer(() -> Mono.just(account.getId().equals(12)));
 
- }
 
- ----
 
- ======
 
- === Customizing Authorization
 
- Spring Security's `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` ship with rich expression-based support.
 
- [[jc-reactive-method-security-custom-granted-authority-defaults]]
 
- 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
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 
- static GrantedAuthorityDefaults grantedAuthorityDefaults() {
 
- 	return new GrantedAuthorityDefaults("MYPREFIX_");
 
- }
 
- ----
 
- ======
 
- [TIP]
 
- ====
 
- 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]).
 
- ====
 
- [[jc-reactive-method-security-custom-authorization-manager]]
 
- === Custom Authorization Managers
 
- Method authorization is a combination of before- and after-method authorization.
 
- [NOTE]
 
- ====
 
- 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 `@EnableReactiveMethodSecurity(useAuthorizationManager=true)` does by default, you would publish the following configuration:
 
- .Full Pre-post Method Security Configuration
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Configuration
 
- class MethodSecurityConfig {
 
- 	@Bean
 
- 	BeanDefinitionRegistryPostProcessor aopConfig() {
 
- 		return AopConfigUtils::registerAutoProxyCreatorIfNecessary;
 
- 	}
 
- 	@Bean
 
- 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 
- 	PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor() {
 
- 		return new PreFilterAuthorizationReactiveMethodInterceptor();
 
- 	}
 
- 	@Bean
 
- 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 
- 	AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor() {
 
- 		return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize();
 
- 	}
 
- 	@Bean
 
- 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 
- 	AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor() {
 
- 		return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize();
 
- 	}
 
- 	@Bean
 
- 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 
- 	PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor() {
 
- 		return new PostFilterAuthorizationReactiveMethodInterceptor();
 
- 	}
 
- }
 
- ----
 
- ======
 
- 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
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Bean
 
- @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 
- Advisor postFilterAuthorizationMethodInterceptor() {
 
- 	PostFilterAuthorizationMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
 
- 	interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
 
- 	return interceptor;
 
- }
 
- ----
 
- ======
 
- You may want to only support `@PreAuthorize` in your application, in which case you can do the following:
 
- .Only @PreAuthorize Configuration
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Configuration
 
- class MethodSecurityConfig {
 
- 	@Bean
 
- 	BeanDefinitionRegistryPostProcessor aopConfig() {
 
- 		return AopConfigUtils::registerAutoProxyCreatorIfNecessary;
 
- 	}
 
- 	@Bean
 
- 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 
- 	Advisor preAuthorize() {
 
- 		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
 
- 	}
 
- }
 
- ----
 
- ======
 
- Or, you may have a custom before-method `ReactiveAuthorizationManager` that you want to add to the list.
 
- In this case, you will need to tell Spring Security both the `ReactiveAuthorizationManager` and to which methods and classes your authorization manager applies.
 
- Thus, you can configure Spring Security to invoke your `ReactiveAuthorizationManager` in between `@PreAuthorize` and `@PostAuthorize` like so:
 
- .Custom Before Advisor
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @EnableReactiveMethodSecurity(useAuthorizationManager=true)
 
- class MethodSecurityConfig {
 
- 	@Bean
 
- 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 
- 	public Advisor customAuthorize() {
 
- 		JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
 
- 		pattern.setPattern("org.mycompany.myapp.service.*");
 
- 		ReactiveAuthorizationManager<MethodInvocation> rule = AuthorityAuthorizationManager.isAuthenticated();
 
- 		AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(pattern, rule);
 
- 		interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
 
- 		return interceptor;
 
-     }
 
- }
 
- ----
 
- ======
 
- [TIP]
 
- ====
 
- 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
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- public interface BankService {
 
- 	@PreAuthorize("hasRole('USER')")
 
- 	@PostAuthorize("returnObject.owner == authentication.name")
 
- 	Mono<Account> readAccount(Long id);
 
- }
 
- ----
 
- ======
 
- 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
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @EnableReactiveMethodSecurity(useAuthorizationManager=true)
 
- class MethodSecurityConfig {
 
- 	@Bean
 
- 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 
- 	public Advisor customAuthorize(ReactiveAuthorizationManager<MethodInvocationResult> rules) {
 
- 		AnnotationMethodMatcher pattern = new AnnotationMethodMatcher(MySecurityAnnotation.class);
 
- 		AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(pattern, rules);
 
- 		interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
 
- 		return interceptor;
 
- 	}
 
- }
 
- ----
 
- ======
 
- and it will be invoked after the `@PostAuthorize` interceptor.
 
- == EnableReactiveMethodSecurity
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
 
- Mono<String> messageByUsername = ReactiveSecurityContextHolder.getContext()
 
- 	.map(SecurityContext::getAuthentication)
 
- 	.map(Authentication::getName)
 
- 	.flatMap(this::findMessageByUsername)
 
- 	// In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
 
- 	.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));
 
- StepVerifier.create(messageByUsername)
 
- 	.expectNext("Hi user")
 
- 	.verifyComplete();
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
 
- val messageByUsername: Mono<String> = ReactiveSecurityContextHolder.getContext()
 
- 	.map(SecurityContext::getAuthentication)
 
- 	.map(Authentication::getName)
 
- 	.flatMap(this::findMessageByUsername) // In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
 
- 	.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
 
- StepVerifier.create(messageByUsername)
 
- 	.expectNext("Hi user")
 
- 	.verifyComplete()
 
- ----
 
- ======
 
- Where `this::findMessageByUsername` is defined as:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- Mono<String> findMessageByUsername(String username) {
 
- 	return Mono.just("Hi " + username);
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- fun findMessageByUsername(username: String): Mono<String> {
 
- 	return Mono.just("Hi $username")
 
- }
 
- ----
 
- ======
 
- The following minimal method security configures method security in reactive applications:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Configuration
 
- @EnableReactiveMethodSecurity
 
- public class SecurityConfig {
 
- 	@Bean
 
- 	public MapReactiveUserDetailsService userDetailsService() {
 
- 		User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
 
- 		UserDetails rob = userBuilder.username("rob")
 
- 			.password("rob")
 
- 			.roles("USER")
 
- 			.build();
 
- 		UserDetails admin = userBuilder.username("admin")
 
- 			.password("admin")
 
- 			.roles("USER","ADMIN")
 
- 			.build();
 
- 		return new MapReactiveUserDetailsService(rob, admin);
 
- 	}
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Configuration
 
- @EnableReactiveMethodSecurity
 
- class SecurityConfig {
 
- 	@Bean
 
- 	fun userDetailsService(): MapReactiveUserDetailsService {
 
- 		val userBuilder: User.UserBuilder = User.withDefaultPasswordEncoder()
 
- 		val rob = userBuilder.username("rob")
 
- 			.password("rob")
 
- 			.roles("USER")
 
- 			.build()
 
- 		val admin = userBuilder.username("admin")
 
- 			.password("admin")
 
- 			.roles("USER", "ADMIN")
 
- 			.build()
 
- 		return MapReactiveUserDetailsService(rob, admin)
 
- 	}
 
- }
 
- ----
 
- ======
 
- Consider the following class:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Component
 
- public class HelloWorldMessageService {
 
- 	@PreAuthorize("hasRole('ADMIN')")
 
- 	public Mono<String> findMessage() {
 
- 		return Mono.just("Hello World!");
 
- 	}
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Component
 
- class HelloWorldMessageService {
 
- 	@PreAuthorize("hasRole('ADMIN')")
 
- 	fun findMessage(): Mono<String> {
 
- 		return Mono.just("Hello World!")
 
- 	}
 
- }
 
- ----
 
- ======
 
- Alternatively, the following class uses Kotlin coroutines:
 
- [tabs]
 
- ======
 
- Kotlin::
 
- +
 
- [source,kotlin,role="primary"]
 
- ----
 
- @Component
 
- class HelloWorldMessageService {
 
-     @PreAuthorize("hasRole('ADMIN')")
 
-     suspend fun findMessage(): String {
 
-         delay(10)
 
-         return "Hello World!"
 
-     }
 
- }
 
- ----
 
- ======
 
- 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.
 
- This means that the expression must not block.
 
- 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:
 
- [tabs]
 
- ======
 
- Java::
 
- +
 
- [source,java,role="primary"]
 
- ----
 
- @Configuration
 
- @EnableWebFluxSecurity
 
- @EnableReactiveMethodSecurity
 
- public class SecurityConfig {
 
- 	@Bean
 
- 	SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
 
- 		return http
 
- 			// Demonstrate that method security works
 
- 			// Best practice to use both for defense in depth
 
- 			.authorizeExchange(exchanges -> exchanges
 
- 				.anyExchange().permitAll()
 
- 			)
 
- 			.httpBasic(withDefaults())
 
- 			.build();
 
- 	}
 
- 	@Bean
 
- 	MapReactiveUserDetailsService userDetailsService() {
 
- 		User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
 
- 		UserDetails rob = userBuilder.username("rob")
 
- 			.password("rob")
 
- 			.roles("USER")
 
- 			.build();
 
- 		UserDetails admin = userBuilder.username("admin")
 
- 			.password("admin")
 
- 			.roles("USER","ADMIN")
 
- 			.build();
 
- 		return new MapReactiveUserDetailsService(rob, admin);
 
- 	}
 
- }
 
- ----
 
- Kotlin::
 
- +
 
- [source,kotlin,role="secondary"]
 
- ----
 
- @Configuration
 
- @EnableWebFluxSecurity
 
- @EnableReactiveMethodSecurity
 
- class SecurityConfig {
 
- 	@Bean
 
- 	open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 
- 		return http {
 
- 			authorizeExchange {
 
- 				authorize(anyExchange, permitAll)
 
- 			}
 
- 			httpBasic { }
 
- 		}
 
- 	}
 
- 	@Bean
 
- 	fun userDetailsService(): MapReactiveUserDetailsService {
 
- 		val userBuilder: User.UserBuilder = User.withDefaultPasswordEncoder()
 
- 		val rob = userBuilder.username("rob")
 
- 			.password("rob")
 
- 			.roles("USER")
 
- 			.build()
 
- 		val admin = userBuilder.username("admin")
 
- 			.password("admin")
 
- 			.roles("USER", "ADMIN")
 
- 			.build()
 
- 		return MapReactiveUserDetailsService(rob, admin)
 
- 	}
 
- }
 
- ----
 
- ======
 
- You can find a complete sample in {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method].
 
 
  |