EnableReactiveMethodSecurity
Spring Security 通过使用 Reactor 的 Context 来支持方法安全性,该 Context 由ReactiveSecurityContextHolder.
以下示例显示如何检索当前登录用户的消息:
| 
 要使此示例正常工作,该方法的返回类型必须是  | 
EnableReactiveMethodSecurity 与 AuthorizationManager
在 Spring Security 5.8 中,我们可以使用@EnableReactiveMethodSecurity(useAuthorizationManager=true)对任何@Configuration实例。
这@EnableReactiveMethodSecurity以多种方式。@EnableReactiveMethodSecurity(useAuthorizationManager=true):
- 
使用简化的
AuthorizationManagerAPI 而不是元数据源、配置属性、决策管理器和投票者。 这简化了重用和定制。 - 
支持反应式返回类型,包括 Kotlin 协程。
 - 
使用原生 Spring AOP 构建,去除抽象并允许您使用 Spring AOP 构建块进行自定义
 - 
检查冲突的注释以确保安全配置明确
 - 
符合 JSR-250
 
| 
 对于早期版本,请阅读 @EnableReactiveMethodSecurity 的类似支持。  | 
例如,以下将启用 Spring Security 的@PreAuthorize注解:
- 
Java
 
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
public class MethodSecurityConfig {
	// ...
}
然后,向方法添加注释(在类或接口上)将相应地限制对该方法的访问。
Spring Security 的本机 Comments 支持为该方法定义了一组属性。
这些将被传递给各种方法拦截器,例如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)));
}
方法授权是方法授权之前和方法之后授权的组合。
| 
 Before-method 授权在调用方法之前执行。
如果该授权拒绝访问,则不会调用该方法,并且  | 
要重新创建添加@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 构建的。
自定义授权
Spring Security 的@PreAuthorize,@PostAuthorize,@PreFilter和@PostFilter附带基于丰富表达式的支持。
- 
Java
 
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("MYPREFIX_");
}
| 
 我们揭露  | 
以编程方式授权方法
如您所见,有几种方法可以使用方法安全性 SPEL 表达式指定重要的授权规则。
有多种方法可以让你的逻辑基于 Java 而不是基于 SPEL。 这为整个 Java 语言提供了 use 访问权限,以提高可测试性和流控制。
在 SPEL 中使用自定义 Bean
以编程方式授权方法的第一种方法是一个两步过程。
首先,声明一个 bean,该 bean 具有一个方法,该方法采用MethodSecurityExpressionOperations实例,如下所示:
- 
Java
 - 
Kotlin
 
@Component("authz")
public class AuthorizationLogic {
    public decide(MethodSecurityExpressionOperations operations): Mono<Boolean> {
        // ... authorization logic
    }
}
@Component("authz")
open class AuthorizationLogic {
    fun decide(val operations: MethodSecurityExpressionOperations): Mono<Boolean> {
        // ... authorization logic
    }
}
然后,按以下方式在 Comments 中引用该 bean:
- 
Java
 - 
Kotlin
 
@Controller
public class MyController {
    @PreAuthorize("@authz.decide(#root)")
    @GetMapping("/endpoint")
    public Mono<String> endpoint() {
        // ...
    }
}
@Controller
open class MyController {
    @PreAuthorize("@authz.decide(#root)")
    @GetMapping("/endpoint")
    fun endpoint(): Mono<String> {
        // ...
    }
}
Spring Security 将为每个方法调用在该 bean 上调用给定的方法。
这样做的好处是,您的所有授权逻辑都位于一个单独的类中,该类可以独立进行单元测试和正确性验证。 它还可以访问完整的 Java 语言。
除了返回Mono<Boolean>,您还可以返回Mono.empty()以指示 Code 放弃做出决策。 | 
如果要包含有关决策性质的更多信息,可以改为返回自定义AuthorizationDecision喜欢这个:
- 
Java
 - 
Kotlin
 
@Component("authz")
public class AuthorizationLogic {
    public Mono<AuthorizationDecision> decide(MethodSecurityExpressionOperations operations) {
        // ... authorization logic
        return Mono.just(new MyAuthorizationDecision(false, details));
    }
}
@Component("authz")
open class AuthorizationLogic {
    fun decide(val operations: MethodSecurityExpressionOperations): Mono<AuthorizationDecision> {
        // ... authorization logic
        return Mono.just(MyAuthorizationDecision(false, details))
    }
}
或者抛出自定义AuthorizationDeniedException实例。
但请注意,返回对象是首选,因为这不会产生生成堆栈跟踪的费用。
然后,您可以在自定义授权结果的处理方式时访问自定义详细信息。
使用自定义授权管理器
以编程方式授权方法的第二种方法是创建自定义AuthorizationManager.
首先,声明一个授权 Management 器实例,可能像这样:
- 
Java
 - 
Kotlin
 
@Component
public class MyPreAuthorizeAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
    @Override
    public Mono<AuthorizationDecision> check(Supplier<Authentication> authentication, MethodInvocation invocation) {
        // ... authorization logic
    }
}
@Component
class MyPreAuthorizeAuthorizationManager : ReactiveAuthorizationManager<MethodInvocation> {
    override fun check(authentication: Supplier<Authentication>, invocation: MethodInvocation): Mono<AuthorizationDecision> {
        // ... authorization logic
    }
}
然后,使用与所需时间相对应的切入点发布方法 interceptorReactiveAuthorizationManager运行。
例如,您可以将@PreAuthorize和@PostAuthorize像这样工作:
- 
Java
 - 
Kotlin
 
@Configuration
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
    @Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor preAuthorize(MyPreAuthorizeAuthorizationManager manager) {
		return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(manager);
	}
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor postAuthorize(MyPostAuthorizeAuthorizationManager manager) {
		return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(manager);
	}
}
@Configuration
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
   	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	fun preAuthorize(val manager: MyPreAuthorizeAuthorizationManager) : Advisor {
		return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(manager)
	}
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	fun postAuthorize(val manager: MyPostAuthorizeAuthorizationManager) : Advisor {
		return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(manager)
	}
}
| 
 你可以使用 Order 常量中指定的 order 常量将拦截器放置在 Spring Security 方法拦截器之间  | 
自定义表达式处理
或者,第三种,您可以自定义每个 SPEL 表达式的处理方式。
为此,您可以公开自定义MethodSecurityExpressionHandler这样:
- 
Java
 - 
Kotlin
 
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
	DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
	handler.setRoleHierarchy(roleHierarchy);
	return handler;
}
companion object {
	@Bean
	fun methodSecurityExpressionHandler(val roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler {
		val handler = DefaultMethodSecurityExpressionHandler()
		handler.setRoleHierarchy(roleHierarchy)
		return handler
	}
}
| 
 我们揭露  | 
你也可以子类化DefaultMessageSecurityExpressionHandler以添加您自己的自定义授权表达式,而不是默认值。
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')")确保findByMessage只能由具有ADMIN角色。
请注意,标准方法安全性中的任何表达式都适用于@EnableReactiveMethodSecurity.
但是,目前,我们只支持Boolean或boolean的表达式。
这意味着表达式不能阻塞。
与 WebFlux Security 集成时,Spring Security 会根据经过身份验证的用户自动建立 Reactor 上下文:
- 
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 中找到完整的示例。