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)));
}
メソッド認証は、メソッド前とメソッド後の認証を組み合わせたものです。
メソッド前の認可は、メソッドが呼び出される前に実行されます。その認可がアクセスを拒否した場合、メソッドは呼び出されず、 |
@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 式を使用して重要な認可ルールを指定できる方法はいくつかあります。
ロジックを SpEL ベースではなく Java ベースにする方法はいくつかあります。これにより、Java 言語全体にアクセスできるようになり、テスト容易性とフロー制御が向上します。
SpEL でのカスタム Bean の使用
メソッドをプログラム的に認証する最初の方法は、2 段階のプロセスです。
まず、次のように MethodSecurityExpressionOperations
インスタンスを受け取るメソッドを持つ Bean を宣言します。
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
}
}
次に、次の方法でアノテーション内でその 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() を返すこともできます。 |
決定の性質に関する詳細情報を含める場合は、代わりに次のようなカスタム 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
インスタンスをスローします。ただし、スタックトレースを生成するコストが発生しないため、オブジェクトを返すことが推奨されることに注意してください。
その後、認可結果の処理方法をカスタマイズするときに、カスタム詳細にアクセスできます。
カスタム認証マネージャーの使用
メソッドをプログラム的に承認する 2 番目の方法は、カスタム AuthorizationManager
を作成することです。
まず、次のように認可マネージャーインスタンスを宣言します。
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
}
}
次に、ReactiveAuthorizationManager
を実行するタイミングに対応するポイントカットを使用してメソッドインターセプターを公開します。例: @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)
}
}
|
式処理のカスタマイズ
または、3 番目に、各 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 セキュリティと統合する場合、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] (英語) ます。