EnableReactiveMethodSecurity
Spring Security使用 Reactor’s Context支持方法安全性,它由`ReactiveSecurityContextHolder`设置。以下示例演示如何检索当前登录用户的消息:
Spring Security supports method security by using Reactor’s Context, which is set up by ReactiveSecurityContextHolder
.
The following example shows how to retrieve the currently logged in user’s message:
要使此示例正常工作,该方法的返回类型必须为 For this example to work, the return type of the method must be a |
EnableReactiveMethodSecurity with AuthorizationManager
在 Spring Security 5.8 中,我们可以使用 @EnableReactiveMethodSecurity(useAuthorizationManager=true)
注解在任何 @Configuration
实例上启用基于注解的安全性。
In Spring Security 5.8, we can enable annotation-based security using the @EnableReactiveMethodSecurity(useAuthorizationManager=true)
annotation on any @Configuration
instance.
这在多个方面改进了 @EnableReactiveMethodSecurity
:
This improves upon @EnableReactiveMethodSecurity
in a number of ways. @EnableReactiveMethodSecurity(useAuthorizationManager=true)
:
-
Uses the simplified
AuthorizationManager
API instead of metadata sources, config attributes, decision managers, and voters. This simplifies reuse and customization. -
Supports reactive return types. Note that we are waiting on additional coroutine support from the Spring Framework before adding coroutine support.
-
Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize
-
Checks for conflicting annotations to ensure an unambiguous security configuration
-
Complies with JSR-250
对于早期版本,请阅读 <<@EnableReactiveMethodSecurity,jc-enable-reactive-method-security>> 类似支持。 For earlier versions, please read about similar support with <<@EnableReactiveMethodSecurity,jc-enable-reactive-method-security>>. |
例如,以下内容将启用 Spring Security 的 @PreAuthorize
注解:
For example, the following would enable Spring Security’s @PreAuthorize
annotation:
-
Java
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
public class MethodSecurityConfig {
// ...
}
把一个注释添加到一个方法(在一个类或接口上)会根据此限制该方法的访问权限。Spring Security 的本机注释支持为方法定义一组属性。这些属性会传递到不同的方法拦截器,比如 AuthorizationManagerBeforeReactiveMethodInterceptor
,以便它做出实际决策:
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:
-
Java
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);
}
在这种情况下,hasRole
指在 SecurityExpressionRoot
中找到的方法,该方法由 SpEL 评估引擎调用。
In this case hasRole
refers to the method found in SecurityExpressionRoot
that gets invoked by the SpEL evaluation engine.
@bean
指你已经定义的一个自定义组件,其中 apply
可以返回 Boolean
或 Mono<Boolean>
来指示授权决策。类似这样的 Bean 可能长这样:
@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:
-
Java
@Bean
public Function<Account, Mono<Boolean>> func() {
return (account) -> Mono.defer(() -> Mono.just(account.getId().equals(12)));
}
Customizing Authorization
Spring Security 的 @PreAuthorize
、@PostAuthorize
、@PreFilter
和 @PostFilter
附带丰富的基于表达式的支持。
Spring Security’s @PreAuthorize
, @PostAuthorize
, @PreFilter
, and @PostFilter
ship with rich expression-based support.
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:
-
Java
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
我们使用 We expose |
Custom Authorization Managers
方法授权是方法前置(before)授权和后置(after)授权的组合。
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 |
若要重建 @EnableReactiveMethodSecurity(useAuthorizationManager=true)
默认执行的操作,你可以发布以下配置:
To recreate what adding @EnableReactiveMethodSecurity(useAuthorizationManager=true)
does by default, you would publish the following configuration:
-
Java
@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();
}
}
注意 Spring Security 的方法安全是使用 Spring AOP 构建的。因此,拦截器会根据指定的顺序调用。可以通过在拦截器实例上调用 setOrder
来对其进行自定义,如下所示:
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:
-
Java
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postFilterAuthorizationMethodInterceptor() {
PostFilterAuthorizationMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
return interceptor;
}
你可能只希望在你的应用程序中支持 @PreAuthorize
,在这种情况下,你可以执行以下操作:
You may want to only support @PreAuthorize
in your application, in which case you can do the following:
-
Java
@Configuration
class MethodSecurityConfig {
@Bean
BeanDefinitionRegistryPostProcessor aopConfig() {
return AopConfigUtils::registerAutoProxyCreatorIfNecessary;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preAuthorize() {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
}
}
或者,你有一个自定义方法前置 ReactiveAuthorizationManager
,你希望将它添加到列表中。
Or, you may have a custom before-method ReactiveAuthorizationManager
that you want to add to the list.
在这种情况下,你需要告诉 Spring Security ReactiveAuthorizationManager
以及你的授权管理器应用到的方法和类。
In this case, you will need to tell Spring Security both the ReactiveAuthorizationManager
and to which methods and classes your authorization manager applies.
因此,你可以配置 Spring Security 在 @PreAuthorize
和 @PostAuthorize
之间调用你的 ReactiveAuthorizationManager
,如下所示:
Thus, you can configure Spring Security to invoke your ReactiveAuthorizationManager
in between @PreAuthorize
and @PostAuthorize
like so:
-
Java
@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;
}
}
您可以使用 You can place your interceptor in between Spring Security method interceptors using the order constants specified in |
同样的操作也可以对方法后置授权执行。方法后置授权通常关心分析返回值以验证访问。
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:
-
Java
public interface BankService {
@PreAuthorize("hasRole('USER')")
@PostAuthorize("returnObject.owner == authentication.name")
Mono<Account> readAccount(Long id);
}
你可以提供你自己的 AuthorizationMethodInterceptor
以自定义如何评估对返回值的访问。
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:
-
Java
@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;
}
}
然后它会在 @PostAuthorize
拦截器之后被调用。
and it will be invoked after the @PostAuthorize
interceptor.
EnableReactiveMethodSecurity
`@EnableReactiveMethodSecurity`还支持Kotlin协程,但仅限于一定程度。在拦截协程时,仅第一个拦截器参与。如果存在任何其他拦截器并在Spring Security方法安全性拦截器之后, they will be skipped。
@EnableReactiveMethodSecurity
also supports Kotlin coroutines, though only to a limited degree.
When intercepting coroutines, only the first interceptor participates.
If any other interceptors are present and come after Spring Security’s method security interceptor, they will be skipped.
-
Java
-
Kotlin
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();
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()
其中 this::findMessageByUsername
的定义为:
Where this::findMessageByUsername
is defined as:
-
Java
-
Kotlin
Mono<String> findMessageByUsername(String username) {
return Mono.just("Hi " + username);
}
fun findMessageByUsername(username: String): Mono<String> {
return Mono.just("Hi $username")
}
下面的 minimal 方法安全配置方法对响应式应用程序中的安全进行了配置:
The following minimal method security configures method security in reactive applications:
-
Java
-
Kotlin
@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);
}
}
@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:
-
Java
-
Kotlin
@Component
public class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
public Mono<String> findMessage() {
return Mono.just("Hello World!");
}
}
@Component
class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
fun findMessage(): Mono<String> {
return Mono.just("Hello World!")
}
}
或者,下面的类使用 Kotlin 协程:
Alternatively, the following class uses Kotlin coroutines:
-
Kotlin
@Component
class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
suspend fun findMessage(): String {
delay(10)
return "Hello World!"
}
}
与我们的上述配置结合后,@PreAuthorize("hasRole('ADMIN')")
确保只允许具有 ADMIN
角色的用户调用 findByMessage
。只要遵循标准方法安全的任何表达式,@EnableReactiveMethodSecurity
都是可以使用的。但是,目前只支持 Boolean
或 boolean
返回类型的表达式。这意味着表达式不能阻塞。
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.
在与 WebFlux Security集成时,响应式上下文是由 Spring Security 根据已验证的用户自动建立的:
When integrating with WebFlux Security, the Reactor Context is automatically established by Spring Security according to the authenticated user:
-
Java
-
Kotlin
@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);
}
}
@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)
}
}
您可以在https://github.com/spring-projects/spring-security-samples/tree/{gh-tag}/reactive/webflux/java/method[hellowebflux-method]找到一个完整的示例。
You can find a complete sample in hellowebflux-method.