EnableReactiveMethodSecurity
Spring Security は、ReactiveSecurityContextHolder
によって設定された Reactor のコンテキスト (英語) を使用して、メソッドのセキュリティをサポートします。次の例は、現在ログインしているユーザーのメッセージを取得する方法を示しています。
この例が機能するには、メソッドの戻り値の型が |
EnableReactiveMethodSecurity と AuthorizationManager
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
は、SpEL 評価エンジンによって呼び出される SecurityExpressionRoot
にあるメソッドを参照します。
@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();
}
}
または、リストに追加するカスタムの before-method ReactiveAuthorizationManager
がある場合があります。
この場合、ReactiveAuthorizationManager
と、認可マネージャーが適用するメソッドとクラスの両方を Spring Security に通知する必要があります。
次のように、@PreAuthorize
と @PostAuthorize
の間で ReactiveAuthorizationManager
を呼び出すように Spring Security を構成できます。
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 セキュリティと統合する場合、Reactor コンテキストは、認証されたユーザーに応じて 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 にあり [GitHub] (英語) ます。