Spring Security 通过使用 Reactor 的 Context 来支持方法安全性,该上下文由 . 以下示例演示如何检索当前登录的用户的消息:ReactiveSecurityContextHolderSpring中文文档

若要使此示例正常工作,该方法的返回类型必须为 a(即 a 或 a)。 这对于与 Reactor 的 .org.reactivestreams.PublisherMonoFluxContextSpring中文文档

若要使此示例正常工作,该方法的返回类型必须为 a(即 a 或 a)。 这对于与 Reactor 的 .org.reactivestreams.PublisherMonoFluxContextSpring中文文档

使用 AuthorizationManager 的 EnableReactiveMethodSecurity

在 Spring Security 5.8 中,我们可以在任何实例上使用注解来启用基于注解的安全性。@EnableReactiveMethodSecurity(useAuthorizationManager=true)@ConfigurationSpring中文文档

这在许多方面得到了改进。:@EnableReactiveMethodSecurity@EnableReactiveMethodSecurity(useAuthorizationManager=true)Spring中文文档

  1. 使用简化的 API,而不是元数据源、配置属性、决策管理器和投票者。 这简化了重用和自定义。AuthorizationManagerSpring中文文档

  2. 支持响应式返回类型,包括 Kotlin 协程。Spring中文文档

  3. 使用原生 Spring AOP 构建,删除抽象并允许您使用 Spring AOP 构建块进行自定义Spring中文文档

  4. 检查冲突的注释,以确保明确的安全配置Spring中文文档

  5. 符合JSR-250Spring中文文档

对于早期版本,请阅读 @EnableReactiveMethodSecurity 的类似支持。Spring中文文档

例如,以下命令将启用 Spring Security 的注解:@PreAuthorizeSpring中文文档

方法安全配置
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
public class MethodSecurityConfig {
	// ...
}

然后,向方法(在类或接口上)添加注释将相应地限制对该方法的访问。 Spring Security 的本机注解支持为该方法定义了一组属性。 这些将被传递给各种方法拦截器,例如 ,以便它做出实际决定:AuthorizationManagerBeforeReactiveMethodInterceptorSpring中文文档

方法安全注释用法
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);
}

在本例中,是指 SpEL 评估引擎调用的方法。hasRoleSecurityExpressionRootSpring中文文档

@bean指您定义的自定义组件,其中可以返回或指示授权决策。 像这样的 Bean 可能看起来像这样:applyBooleanMono<Boolean>Spring中文文档

方法安全性反应式布尔表达式
@Bean
public Function<Account, Mono<Boolean>> func() {
    return (account) -> Mono.defer(() -> Mono.just(account.getId().equals(12)));
}

自定义授权

Spring Security 的 、 、 和 附带了丰富的基于表达式的支持。@PreAuthorize@PostAuthorize@PreFilter@PostFilterSpring中文文档

此外,对于基于角色的授权,Spring Security 添加了一个默认前缀,用于评估 . 您可以通过公开 Bean 来配置授权规则以使用不同的前缀,如下所示:ROLE_hasRoleGrantedAuthorityDefaultsSpring中文文档

自定义 MethodSecurityExpressionHandler
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("MYPREFIX_");
}

我们使用一种方法来公开它,以确保 Spring 在初始化 Spring Security 的方法安全类之前发布它。 由于 Bean 是 Spring Security 内部工作的一部分,我们还应该将其公开为基础设施 Bean,从而有效地避免一些与 Bean 后处理相关的警告(参见 gh-14751)。GrantedAuthorityDefaultsstatic@ConfigurationGrantedAuthorityDefaultsSpring中文文档

自定义授权管理器

方法授权是方法前授权和方法后授权的组合。Spring中文文档

在调用方法之前执行方法授权。 如果该授权拒绝访问,则不会调用该方法,并抛出 an。 在调用方法之后,但在方法返回给调用方之前执行方法后授权。 如果该授权拒绝访问,则不会返回该值,并抛出AccessDeniedExceptionAccessDeniedExceptionSpring中文文档

若要重新创建默认情况下添加的功能,请发布以下配置:@EnableReactiveMethodSecurity(useAuthorizationManager=true)Spring中文文档

完整的前后方法安全配置
@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 构建的。 因此,拦截器是根据指定的顺序调用的。 这可以通过调用拦截器实例来自定义,如下所示:setOrderSpring中文文档

发布自定义顾问
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postFilterAuthorizationMethodInterceptor() {
	PostFilterAuthorizationMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
	interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
	return interceptor;
}

您可能只想在应用程序中提供支持,在这种情况下,您可以执行以下操作:@PreAuthorizeSpring中文文档

仅@PreAuthorize配置
@Configuration
class MethodSecurityConfig {
	@Bean
	BeanDefinitionRegistryPostProcessor aopConfig() {
		return AopConfigUtils::registerAutoProxyCreatorIfNecessary;
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor preAuthorize() {
		return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
	}
}

或者,您可能有一个要添加到列表中的自定义 before-方法。ReactiveAuthorizationManagerSpring中文文档

在这种情况下,您需要告诉 Spring Security 您的授权管理器应用的方法和类以及适用于哪些方法和类。ReactiveAuthorizationManagerSpring中文文档

因此,您可以将 Spring Security 配置为在两者之间调用您的 in 和如下:ReactiveAuthorizationManager@PreAuthorize@PostAuthorizeSpring中文文档

Custom Before Advisor(顾问前定制)
@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;
    }
}

您可以使用 中指定的顺序常量将拦截器放置在 Spring Security 方法拦截器之间。AuthorizationInterceptorsOrderSpring中文文档

对于方法后授权也可以执行相同的操作。 方法后授权通常涉及分析返回值以验证访问。Spring中文文档

例如,您可能有一种方法可以确认请求的帐户实际上属于登录用户,如下所示:Spring中文文档

@PostAuthorize例子
public interface BankService {

	@PreAuthorize("hasRole('USER')")
	@PostAuthorize("returnObject.owner == authentication.name")
	Mono<Account> readAccount(Long id);
}

您可以提供自己的数据来自定义如何评估对返回值的访问。AuthorizationMethodInterceptorSpring中文文档

例如,如果您有自己的自定义注解,则可以按如下方式进行配置:Spring中文文档

自定义后顾问
@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;
	}
}

它将在拦截器之后被调用。@PostAuthorizeSpring中文文档

对于早期版本,请阅读 @EnableReactiveMethodSecurity 的类似支持。Spring中文文档

我们使用一种方法来公开它,以确保 Spring 在初始化 Spring Security 的方法安全类之前发布它。 由于 Bean 是 Spring Security 内部工作的一部分,我们还应该将其公开为基础设施 Bean,从而有效地避免一些与 Bean 后处理相关的警告(参见 gh-14751)。GrantedAuthorityDefaultsstatic@ConfigurationGrantedAuthorityDefaultsSpring中文文档

在调用方法之前执行方法授权。 如果该授权拒绝访问,则不会调用该方法,并抛出 an。 在调用方法之后,但在方法返回给调用方之前执行方法后授权。 如果该授权拒绝访问,则不会返回该值,并抛出AccessDeniedExceptionAccessDeniedExceptionSpring中文文档

您可以使用 中指定的顺序常量将拦截器放置在 Spring Security 方法拦截器之间。AuthorizationInterceptorsOrderSpring中文文档

EnableReactiveMethodSecurity

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::findMessageByUsernameSpring中文文档

Mono<String> findMessageByUsername(String username) {
	return Mono.just("Hi " + username);
}
fun findMessageByUsername(username: String): Mono<String> {
	return Mono.just("Hi $username")
}

以下最低方法安全性在反应式应用程序中配置方法安全性:Spring中文文档

@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)
	}
}

请考虑以下类:Spring中文文档

@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 协程:Spring中文文档

@Component
class HelloWorldMessageService {
    @PreAuthorize("hasRole('ADMIN')")
    suspend fun findMessage(): String {
        delay(10)
        return "Hello World!"
    }
}

结合我们上面的配置,确保它仅由具有该角色的用户调用。 请注意,标准方法安全性中的任何表达式都适用于 。 但是,目前,我们仅支持表达式的 or 的返回类型。 这意味着表达式不得阻塞。@PreAuthorize("hasRole('ADMIN')")findByMessageADMIN@EnableReactiveMethodSecurityBooleanbooleanSpring中文文档

WebFlux Security 集成时,Spring Security 会根据经过身份验证的用户自动建立 Reactor Context:Spring中文文档

@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 中找到一个完整的示例。Spring中文文档