跳到主要内容
版本:7.0.2

RSocket 安全

DeepSeek V3 中英对照 RSocket RSocket Security

Spring Security 的 RSocket 支持依赖于 SocketAcceptorInterceptor。安全机制的主要入口点是 PayloadSocketAcceptorInterceptor,它适配了 RSocket API,允许通过 PayloadInterceptor 实现来拦截 PayloadExchange

以下示例展示了最小化的 RSocket 安全配置:

最小化 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 中自动注册它。

或者,如果您没有使用 Boot 的自动配置,也可以通过以下方式手动注册:

@Bean
RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInterceptor interceptor) {
return (server) -> server.interceptors((registry) -> registry.forSocketAcceptor(interceptor));
}

要自定义拦截器本身,请使用 RSocketSecurity 来添加身份验证授权

RSocket 认证

RSocket 身份验证通过 AuthenticationPayloadInterceptor 执行,该拦截器作为控制器来调用 ReactiveAuthenticationManager 实例。

设置时认证与请求时认证

通常,身份验证可以在设置时或请求时进行,或者两者兼有。

在几种场景下,在建立连接时进行身份验证是合理的。一个常见场景是当单个用户(例如移动设备连接)使用 RSocket 连接时。在这种情况下,只有一个用户使用该连接,因此可以在连接建立时一次性完成身份验证。

在共享 RSocket 连接的情况下,为每个请求发送凭据是合理的。例如,一个作为下游服务连接到 RSocket 服务器的 Web 应用程序会建立一个所有用户共用的单一连接。在这种情况下,如果 RSocket 服务器需要基于 Web 应用程序的用户凭据执行授权,那么对每个请求进行身份验证是合理的。

在某些场景中,在建立连接时和每次请求时都进行身份验证是有意义的。以前面提到的 Web 应用程序为例。如果我们需要将连接限制在 Web 应用程序本身,可以在连接时提供一个具有 SETUP 权限的凭据。这样,每个用户可以拥有不同的权限,但不能拥有 SETUP 权限。这意味着单个用户可以发出请求,但不能建立额外的连接。

简单认证

备注

Basic Authentication 已演变为 Simple Authentication,目前仅出于向后兼容性考虑而提供支持。如需配置,请参阅 RSocketSecurity.basicAuthentication(Customizer)

RSocket接收器可以通过使用AuthenticationPayloadExchangeConverter来解码凭据,该转换器通过DSL的simpleAuthentication部分自动设置。以下示例展示了显式配置:

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

RSocket发送方可以通过使用SimpleAuthenticationEncoder来发送凭证,你可以将其添加到Spring的RSocketStrategies中。

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 支持 Bearer Token 认证元数据扩展。该支持体现在对 JWT 进行认证(验证 JWT 的有效性),然后使用该 JWT 进行授权决策。

RSocket接收器可以通过使用BearerPayloadExchangeConverter来解码凭证,该转换器通过DSL的jwt部分自动配置。以下清单展示了一个示例配置:

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

上述配置依赖于一个 ReactiveJwtDecoder @Bean 的存在。以下示例展示了如何基于颁发者创建该 Bean:

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

RSocket发送方无需采取特殊操作来发送令牌,因为该值仅为简单的String类型。以下示例展示了在建立连接时发送令牌的方式:

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授权通过AuthorizationPayloadInterceptor执行,它作为控制器来调用ReactiveAuthorizationManager实例。您可以使用DSL基于PayloadExchange设置授权规则。以下清单展示了一个配置示例:

rsocket
.authorizePayload(authz ->
authz
.setup().hasRole("SETUP") 1
.route("fetch.profile.me").authenticated() 2
.matcher((payloadExchange) -> payloadExchange(payloadExchange)) 3
.hasRole("CUSTOM")
.route("fetch.profile.{username}") 4
.access((authentication, context) -> checkFriends(authentication, context))
.anyRequest().authenticated() 5
.anyExchange().permitAll() 6
);
  • 建立连接需要 ROLE_SETUP 权限。

  • 如果路由是 fetch.profile.me,授权仅要求用户已通过身份验证。

  • 在此规则中,我们设置了一个自定义匹配器,授权要求用户拥有 ROLE_CUSTOM 权限。

  • 此规则使用自定义授权。匹配器表达了一个名为 username 的变量,该变量在 context 中可用。自定义授权规则在 checkFriends 方法中公开。

  • 此规则确保尚未拥有规则的请求要求用户已通过身份验证。请求是指包含元数据的情况。它不会包含额外的有效载荷。

  • 此规则确保任何尚未拥有规则的交换对任何人都被允许。在此示例中,这意味着没有元数据的有效载荷也没有授权规则。

请注意,授权规则是按顺序执行的。只有第一个匹配的授权规则会被调用。