EnableReactiveMethodSecurity
Spring Security 透過使用 Reactor 的 Context 來支援方法安全性,Context 由 ReactiveSecurityContextHolder
設置。以下範例示範如何檢索目前登入使用者的訊息
為了使此範例運作,方法的回傳型別必須是 |
使用 AuthorizationManager 啟用 ReactiveMethodSecurity
在 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<Boolean>
以指示授權決策。這樣的 Bean 可能看起來像這樣
-
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')")
確保 findByMessage
僅由具有 ADMIN
角色的使用者調用。請注意,標準方法安全性中的任何運算式都適用於 @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 中找到完整的範例。