跳到主要内容
版本: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 方法中公开。

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

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

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