最新の安定バージョンについては、Spring Security 6.4.5 を使用してください! |
RSocket セキュリティ
Spring Security の RSocket サポートは SocketAcceptorInterceptor
に依存しています。セキュリティへの主要なエントリポイントは、PayloadInterceptor
実装で PayloadExchange
をインターセプトできるように RSocket API を適合させる PayloadSocketAcceptorInterceptor
にあります。
以下のコードを示すサンプルアプリケーションをいくつか見つけることができます。
Hello RSocket hellorsocket [GitHub] (英語)
最小限の RSocket セキュリティ構成
以下に、最小限の RSocket セキュリティ構成を示します。
Java
Kotlin
@Configuration
@EnableRSocketSecurity
public class HelloRSocketSecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new MapReactiveUserDetailsService(user);
}
}
@Configuration
@EnableRSocketSecurity
open class HelloRSocketSecurityConfig {
@Bean
open fun userDetailsService(): MapReactiveUserDetailsService {
val user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build()
return MapReactiveUserDetailsService(user)
}
}
この構成により、単純な認証が可能になり、rsocket-authorization がセットアップされて、リクエストに対して認証されたユーザーが必要になります。
SecuritySocketAcceptorInterceptor の追加
Spring Security が機能するには、SecuritySocketAcceptorInterceptor
を ServerRSocketFactory
に適用する必要があります。これが、作成した PayloadSocketAcceptorInterceptor
と RSocket インフラストラクチャを接続するものです。Spring Boot アプリケーションでは、これは次のコードで RSocketSecurityAutoConfiguration
を使用して自動的に行われます。
Java
Kotlin
@Bean
RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInterceptor interceptor) {
return (server) -> server.interceptors((registry) -> registry.forSocketAcceptor(interceptor));
}
@Bean
fun springSecurityRSocketSecurity(interceptor: SecuritySocketAcceptorInterceptor): RSocketServerCustomizer {
return RSocketServerCustomizer { server ->
server.interceptors { registry ->
registry.forSocketAcceptor(interceptor)
}
}
}
RSocket 認証
RSocket 認証は、ReactiveAuthenticationManager
インスタンスを呼び出すためのコントローラーとして機能する AuthenticationPayloadInterceptor
で実行されます。
セットアップ時の認証とリクエスト時
一般に、認証はセットアップ時および / またはリクエスト時に発生する可能性があります。
セットアップ時の認証は、いくつかのシナリオで意味があります。一般的なシナリオは、1 人のユーザー(モバイル接続)が RSocket 接続を活用している場合です。この場合、1 人のユーザーのみが接続を利用しているため、接続時に認証を 1 回実行できます。
RSocket 接続が共有されるシナリオでは、各リクエストで資格情報を送信することは理にかなっています。例: RSocket サーバーにダウンストリームサービスとして接続する Web アプリケーションは、すべてのユーザーが利用する単一の接続を作成します。この場合、RSocket サーバーがリクエストごとの Web アプリケーションのユーザー資格情報に基づいて認可を実行する必要がある場合は、意味があります。
一部のシナリオでは、セットアップ時およびリクエストごとの認証が有効です。前述の Web アプリケーションを検討してください。Web アプリケーション自体への接続を制限する必要がある場合は、接続時に SETUP
権限を持つ資格情報を提供できます。その場合、各ユーザーには異なる権限がありますが、SETUP
権限はありません。これは、個々のユーザーはリクエストを行うことはできますが、追加の接続はできないことを意味します。
単純認証
Spring Security はシンプル認証メタデータ拡張 [GitHub] (英語) をサポートしています。
基本認証ドラフトは単純認証に進化し、下位互換性のためにのみサポートされています。設定については、 |
RSocket レシーバーは、DSL の simpleAuthentication
部分を使用して自動的にセットアップされる AuthenticationPayloadExchangeConverter
を使用して資格情報をデコードできます。明示的な構成は以下にあります。
Java
Kotlin
@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
rsocket
.authorizePayload(authorize ->
authorize
.anyRequest().authenticated()
.anyExchange().permitAll()
)
.simpleAuthentication(Customizer.withDefaults());
return rsocket.build();
}
@Bean
open fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
rsocket
.authorizePayload { authorize -> authorize
.anyRequest().authenticated()
.anyExchange().permitAll()
}
.simpleAuthentication(withDefaults())
return rsocket.build()
}
RSocket 送信者は、Spring の RSocketStrategies
に追加できる SimpleAuthenticationEncoder
を使用して資格情報を送信できます。
Java
Kotlin
RSocketStrategies.Builder strategies = ...;
strategies.encoder(new SimpleAuthenticationEncoder());
var strategies: RSocketStrategies.Builder = ...
strategies.encoder(SimpleAuthenticationEncoder())
次に、セットアップで受信者にユーザー名とパスワードを送信するために使用できます。
Java
Kotlin
MimeType authenticationMimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
Mono<RSocketRequester> requester = RSocketRequester.builder()
.setupMetadata(credentials, authenticationMimeType)
.rsocketStrategies(strategies.build())
.connectTcp(host, port);
val authenticationMimeType: MimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
val credentials = UsernamePasswordMetadata("user", "password")
val requester: Mono<RSocketRequester> = RSocketRequester.builder()
.setupMetadata(credentials, authenticationMimeType)
.rsocketStrategies(strategies.build())
.connectTcp(host, port)
代替的または追加的に、リクエストでユーザー名とパスワードを送信できます。
Java
Kotlin
Mono<RSocketRequester> requester;
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
public Mono<AirportLocation> findRadar(String code) {
return this.requester.flatMap(req ->
req.route("find.radar.{code}", code)
.metadata(credentials, authenticationMimeType)
.retrieveMono(AirportLocation.class)
);
}
import org.springframework.messaging.rsocket.retrieveMono
// ...
var requester: Mono<RSocketRequester>? = null
var credentials = UsernamePasswordMetadata("user", "password")
open fun findRadar(code: String): Mono<AirportLocation> {
return requester!!.flatMap { req ->
req.route("find.radar.{code}", code)
.metadata(credentials, authenticationMimeType)
.retrieveMono<AirportLocation>()
}
}
JWT
Spring Security はベアラートークン認証メタデータ拡張 [GitHub] (英語) をサポートしています。サポートは、JWT を認証し(JWT が有効であると判断する)、JWT を使用して認可決定を行うという形で提供されます。
RSocket レシーバーは、DSL の jwt
部分を使用して自動的にセットアップされる BearerPayloadExchangeConverter
を使用して資格情報をデコードできます。以下に設定例を示します。
Java
Kotlin
@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
rsocket
.authorizePayload(authorize ->
authorize
.anyRequest().authenticated()
.anyExchange().permitAll()
)
.jwt(Customizer.withDefaults());
return rsocket.build();
}
@Bean
fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
rsocket
.authorizePayload { authorize -> authorize
.anyRequest().authenticated()
.anyExchange().permitAll()
}
.jwt(withDefaults())
return rsocket.build()
}
上記の構成は、ReactiveJwtDecoder
@Bean
が存在することに依存しています。発行者から作成する例を以下に示します。
Java
Kotlin
@Bean
ReactiveJwtDecoder jwtDecoder() {
return ReactiveJwtDecoders
.fromIssuerLocation("https://example.com/auth/realms/demo");
}
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return ReactiveJwtDecoders
.fromIssuerLocation("https://example.com/auth/realms/demo")
}
値は単なる文字列であるため、RSocket 送信者はトークンを送信するために特別なことをする必要はありません。例: セットアップ時にトークンを送信できます:
Java
Kotlin
MimeType authenticationMimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
BearerTokenMetadata token = ...;
Mono<RSocketRequester> requester = RSocketRequester.builder()
.setupMetadata(token, authenticationMimeType)
.connectTcp(host, port);
val authenticationMimeType: MimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
val token: BearerTokenMetadata = ...
val requester = RSocketRequester.builder()
.setupMetadata(token, authenticationMimeType)
.connectTcp(host, port)
代替的または追加的に、トークンをリクエストで送信できます。
Java
Kotlin
MimeType authenticationMimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
Mono<RSocketRequester> requester;
BearerTokenMetadata token = ...;
public Mono<AirportLocation> findRadar(String code) {
return this.requester.flatMap(req ->
req.route("find.radar.{code}", code)
.metadata(token, authenticationMimeType)
.retrieveMono(AirportLocation.class)
);
}
val authenticationMimeType: MimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
var requester: Mono<RSocketRequester>? = null
val token: BearerTokenMetadata = ...
open fun findRadar(code: String): Mono<AirportLocation> {
return this.requester!!.flatMap { req ->
req.route("find.radar.{code}", code)
.metadata(token, authenticationMimeType)
.retrieveMono<AirportLocation>()
}
}
RSocket 認証
RSocket 認証は、ReactiveAuthorizationManager
インスタンスを呼び出すコントローラーとして機能する AuthorizationPayloadInterceptor
で実行されます。DSL を使用して、PayloadExchange
に基づいて認可規則を設定できます。以下に設定例を示します。
Java
Kotlin
rsocket
.authorizePayload(authz ->
authz
.setup().hasRole("SETUP") (1)
.route("fetch.profile.me").authenticated() (2)
.matcher(payloadExchange -> isMatch(payloadExchange)) (3)
.hasRole("CUSTOM")
.route("fetch.profile.{username}") (4)
.access((authentication, context) -> checkFriends(authentication, context))
.anyRequest().authenticated() (5)
.anyExchange().permitAll() (6)
);
rsocket
.authorizePayload { authz ->
authz
.setup().hasRole("SETUP") (1)
.route("fetch.profile.me").authenticated() (2)
.matcher { payloadExchange -> isMatch(payloadExchange) } (3)
.hasRole("CUSTOM")
.route("fetch.profile.{username}") (4)
.access { authentication, context -> checkFriends(authentication, context) }
.anyRequest().authenticated() (5)
.anyExchange().permitAll()
} (6)
1 | 接続を設定するには、権限 ROLE_SETUP が必要です |
2 | ルートが fetch.profile.me 認可の場合、ユーザーの認証のみが必要です |
3 | このルールでは、認可にユーザーに権限 ROLE_CUSTOM が必要なカスタムマッチャーを設定します |
4 | このルールはカスタム認証を活用します。マッチャーは、username という名前の変数を表現します。これは、context で使用可能になります。カスタム認証ルールは、checkFriends メソッドで公開されます。 |
5 | このルールにより、まだルールを持たないリクエストでは、ユーザーの認証が必要になります。リクエストは、メタデータが含まれる場所です。追加のペイロードは含まれません。 |
6 | このルールにより、ルールをまだ持っていない交換は誰でも認可されます。この例では、メタデータのないペイロードには認可ルールがないことを意味します。 |
認可ルールは順番に実行されることを理解することが重要です。一致する最初の認可ルールのみが呼び出されます。