最新の安定バージョンについては、Spring Security 6.4.5 を使用してください!

RSocket セキュリティ

Spring Security の RSocket サポートは SocketAcceptorInterceptor に依存しています。セキュリティへの主要なエントリポイントは、PayloadInterceptor 実装で PayloadExchange をインターセプトできるように RSocket API を適合させる PayloadSocketAcceptorInterceptor にあります。

以下のコードを示すサンプルアプリケーションをいくつか見つけることができます。

最小限の RSocket セキュリティ構成

以下に、最小限の RSocket セキュリティ構成を示します。

@Configuration
@EnableRSocketSecurity
public class HelloRSocketSecurityConfig {

	@Bean
	public MapReactiveUserDetailsService userDetailsService() {
		UserDetails user = User.withDefaultPasswordEncoder()
			.username("user")
			.password("user")
			.roles("USER")
			.build();
		return new MapReactiveUserDetailsService(user);
	}
}

この構成により、単純な認証が可能になり、rsocket-authorization がセットアップされて、リクエストに対して認証されたユーザーが必要になります。

SecuritySocketAcceptorInterceptor の追加

Spring Security が機能するには、SecuritySocketAcceptorInterceptor を ServerRSocketFactory に適用する必要があります。これが、作成した PayloadSocketAcceptorInterceptor と RSocket インフラストラクチャを接続するものです。Spring Boot アプリケーションでは、これは次のコードで RSocketSecurityAutoConfiguration を使用して自動的に行われます。

@Bean
RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInterceptor interceptor) {
    return (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] (英語) をサポートしています。

基本認証ドラフトは単純認証に進化し、下位互換性のためにのみサポートされています。設定については、RSocketSecurity.basicAuthentication(Customizer) を参照してください。

RSocket レシーバーは、DSL の simpleAuthentication 部分を使用して自動的にセットアップされる AuthenticationPayloadExchangeConverter を使用して資格情報をデコードできます。明示的な構成は以下にあります。

@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
	rsocket
		.authorizePayload(authorize ->
			authorize
					.anyRequest().authenticated()
					.anyExchange().permitAll()
		)
		.simpleAuthentication(Customizer.withDefaults());
	return rsocket.build();
}

RSocket 送信者は、Spring の RSocketStrategies に追加できる SimpleAuthenticationEncoder を使用して資格情報を送信できます。

RSocketStrategies.Builder strategies = ...;
strategies.encoder(new SimpleAuthenticationEncoder());

次に、セットアップで受信者にユーザー名とパスワードを送信するために使用できます。

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);

代替的または追加的に、リクエストでユーザー名とパスワードを送信できます。

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

JWT

Spring Security はベアラートークン認証メタデータ拡張 [GitHub] (英語) をサポートしています。サポートは、JWT を認証し(JWT が有効であると判断する)、JWT を使用して認可決定を行うという形で提供されます。

RSocket レシーバーは、DSL の jwt 部分を使用して自動的にセットアップされる BearerPayloadExchangeConverter を使用して資格情報をデコードできます。以下に設定例を示します。

@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
	rsocket
		.authorizePayload(authorize ->
			authorize
				.anyRequest().authenticated()
				.anyExchange().permitAll()
		)
		.jwt(Customizer.withDefaults());
	return rsocket.build();
}

上記の構成は、ReactiveJwtDecoder @Bean が存在することに依存しています。発行者から作成する例を以下に示します。

@Bean
ReactiveJwtDecoder jwtDecoder() {
	return ReactiveJwtDecoders
		.fromIssuerLocation("https://example.com/auth/realms/demo");
}

値は単なる文字列であるため、RSocket 送信者はトークンを送信するために特別なことをする必要はありません。例: セットアップ時にトークンを送信できます:

MimeType authenticationMimeType =
	MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
BearerTokenMetadata token = ...;
Mono<RSocketRequester> requester = RSocketRequester.builder()
	.setupMetadata(token, authenticationMimeType)
	.connectTcp(host, port);

代替的または追加的に、トークンをリクエストで送信できます。

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

RSocket 認証

RSocket 認証は、ReactiveAuthorizationManager インスタンスを呼び出すコントローラーとして機能する AuthorizationPayloadInterceptor で実行されます。DSL を使用して、PayloadExchange に基づいて認可規則を設定できます。以下に設定例を示します。

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 このルールにより、ルールをまだ持っていない交換は誰でも認可されます。この例では、メタデータのないペイロードには認可ルールがないことを意味します。

認可ルールは順番に実行されることを理解することが重要です。一致する最初の認可ルールのみが呼び出されます。