EnableReactiveMethodSecurity
Spring Security使用 Reactor’s Context支持方法安全性,它由`ReactiveSecurityContextHolder`设置。以下示例演示如何检索当前登录用户的消息:
要使此示例正常工作,该方法的返回类型必须为 |
EnableReactiveMethodSecurity with AuthorizationManager
在 Spring Security 5.8 中,我们可以使用 @EnableReactiveMethodSecurity(useAuthorizationManager=true)
注解在任何 @Configuration
实例上启用基于注解的安全性。
这在多个方面改进了 @EnableReactiveMethodSecurity
:
-
使用简化的
AuthorizationManager
API,而不是元数据源、配置属性、决策管理器和投票者。这可简化复用和自定义。 -
支持响应式返回类型。注意,在添加协程支持之前,我们正等待 additional coroutine support from the Spring Framework。
-
使用本机 Spring AOP 来构建,这消除了抽象,并允许您使用 Spring AOP 构建基块进行自定义
-
检查是否存在冲突的注解,以确保安全配置明确
-
Complies with JSR-250
对于早期版本,请阅读 <<@EnableReactiveMethodSecurity,jc-enable-reactive-method-security>> 类似支持。 |
例如,以下内容将启用 Spring Security 的 @PreAuthorize
注解:
-
Java
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
public class MethodSecurityConfig {
// ...
}
把一个注释添加到一个方法(在一个类或接口上)会根据此限制该方法的访问权限。Spring Security 的本机注释支持为方法定义一组属性。这些属性会传递到不同的方法拦截器,比如 AuthorizationManagerBeforeReactiveMethodInterceptor
,以便它做出实际决策:
-
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 评估引擎调用。
@bean
指你已经定义的一个自定义组件,其中 apply
可以返回 Boolean
或 Mono<Boolean>
来指示授权决策。类似这样的 Bean 可能长这样:
-
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
附带丰富的基于表达式的支持。
-
Java
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
我们使用 |
Custom Authorization Managers
方法授权是方法前置(before)授权和后置(after)授权的组合。
方法前置授权在调用方法之前执行。如果该授权拒绝访问,则不会调用该方法并会抛出一个 |
若要重建 @EnableReactiveMethodSecurity(useAuthorizationManager=true)
默认执行的操作,你可以发布以下配置:
-
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
来对其进行自定义,如下所示:
-
Java
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postFilterAuthorizationMethodInterceptor() {
PostFilterAuthorizationMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
return interceptor;
}
你可能只希望在你的应用程序中支持 @PreAuthorize
,在这种情况下,你可以执行以下操作:
-
Java
@Configuration
class MethodSecurityConfig {
@Bean
BeanDefinitionRegistryPostProcessor aopConfig() {
return AopConfigUtils::registerAutoProxyCreatorIfNecessary;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preAuthorize() {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
}
}
或者,你有一个自定义方法前置 ReactiveAuthorizationManager
,你希望将它添加到列表中。
在这种情况下,你需要告诉 Spring Security ReactiveAuthorizationManager
以及你的授权管理器应用到的方法和类。
因此,你可以配置 Spring Security 在 @PreAuthorize
和 @PostAuthorize
之间调用你的 ReactiveAuthorizationManager
,如下所示:
-
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;
}
}
您可以使用 |
同样的操作也可以对方法后置授权执行。方法后置授权通常关心分析返回值以验证访问。
例如,你可能有一个方法确认请求的账户实际上属于登录用户,如下所示:
-
Java
public interface BankService {
@PreAuthorize("hasRole('USER')")
@PostAuthorize("returnObject.owner == authentication.name")
Mono<Account> readAccount(Long id);
}
你可以提供你自己的 AuthorizationMethodInterceptor
以自定义如何评估对返回值的访问。
例如,如果你有你自己的自定义注释,你可以对其进行如下配置:
-
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
拦截器之后被调用。
EnableReactiveMethodSecurity
`@EnableReactiveMethodSecurity`还支持Kotlin协程,但仅限于一定程度。在拦截协程时,仅第一个拦截器参与。如果存在任何其他拦截器并在Spring Security方法安全性拦截器之后, 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
的定义为:
-
Java
-
Kotlin
Mono<String> findMessageByUsername(String username) {
return Mono.just("Hi " + username);
}
fun findMessageByUsername(username: String): Mono<String> {
return Mono.just("Hi $username")
}
下面的 minimal 方法安全配置方法对响应式应用程序中的安全进行了配置:
-
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)
}
}
考虑以下类:
-
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 协程:
-
Kotlin
@Component
class HelloWorldMessageService {
@PreAuthorize("hasRole('ADMIN')")
suspend fun findMessage(): String {
delay(10)
return "Hello World!"
}
}
与我们的上述配置结合后,@PreAuthorize("hasRole('ADMIN')")
确保只允许具有 ADMIN
角色的用户调用 findByMessage
。只要遵循标准方法安全的任何表达式,@EnableReactiveMethodSecurity
都是可以使用的。但是,目前只支持 Boolean
或 boolean
返回类型的表达式。这意味着表达式不能阻塞。
在与 WebFlux Security集成时,响应式上下文是由 Spring Security 根据已验证的用户自动建立的:
-
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]找到一个完整的示例。