EnableReactiveMethodSecurity
Spring Security 使用Reactor 的 Context(由 `ReactiveSecurityContextHolder` 设置)支持方法安全。以下示例展示了如何检索当前登录用户的消息
为了使此示例生效,方法的返回类型必须是 `org.reactivestreams.Publisher`(即 `Mono` 或 `Flux`)。这是与 Reactor 的 `Context` 集成的必要条件。 |
使用 AuthorizationManager 的 EnableReactiveMethodSecurity
在 Spring Security 5.8 中,我们可以在任何 `@Configuration` 实例上使用 `@EnableReactiveMethodSecurity(useAuthorizationManager=true)` 注解启用基于注解的安全。
这在许多方面改进了 `@EnableReactiveMethodSecurity`。`@EnableReactiveMethodSecurity(useAuthorizationManager=true)`
-
使用简化的 `AuthorizationManager` API,而不是元数据源、配置属性、决策管理器和投票器。这简化了重用和自定义。
-
支持响应式返回类型,包括 Kotlin 协程。
-
使用原生 Spring AOP 构建,消除了抽象,并允许您使用 Spring AOP 构建块进行自定义。
-
检查冲突的注解,以确保安全配置明确无误。
-
符合 JSR-250。
对于早期版本,请阅读关于使用@EnableReactiveMethodSecurity 的类似支持。 |
例如,以下内容将启用 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
-
Java
@Bean
public Function<Account, Mono<Boolean>> func() {
return (account) -> Mono.defer(() -> Mono.just(account.getId().equals(12)));
}
自定义授权
Spring Security 的 `@PreAuthorize`、`@PostAuthorize`、`@PreFilter` 和 `@PostFilter` 附带丰富的基于表达式的支持。
-
Java
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
我们使用 |
自定义授权管理器
方法授权是方法前授权和方法后授权的组合。
方法前授权在调用方法之前执行。如果该授权拒绝访问,则不调用该方法,并抛出 |
要重现默认情况下添加@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
-
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")
}
以下最小的方法安全配置在反应式应用程序中配置方法安全
-
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集成时,Reactor Context会根据已认证的用户由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)
}
}
您可以在hellowebflux-method中找到完整的示例。