跳到主要内容

RSocket 安全

QWen Max 中英对照 RSocket RSocket Security

Spring Security 的 RSocket 支持依赖于一个 SocketAcceptorInterceptor。安全性的主要入口点在 PayloadSocketAcceptorInterceptor 中,它将 RSocket API 适配为允许使用 PayloadInterceptor 实现拦截 PayloadExchange

以下示例显示了一个最小的RSocket Security配置:

最小的 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);
}
}
java

此配置启用简单认证,并设置rsocket-授权以要求任何请求都需要经过认证的用户。

添加 SecuritySocketAcceptorInterceptor

为了让 Spring Security 正常工作,我们需要将 SecuritySocketAcceptorInterceptor 应用到 ServerRSocketFactory。这样做可以将我们的 PayloadSocketAcceptorInterceptor 与 RSocket 基础设施连接起来。

当你包含正确的依赖项时,Spring Boot 会在 RSocketSecurityAutoConfiguration 中自动注册它。

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

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

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

RSocket 认证

RSocket 身份验证通过 AuthenticationPayloadInterceptor 执行,它充当控制器来调用 ReactiveAuthenticationManager 实例。

设置时与请求时的身份验证

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

在设置时进行身份验证在某些场景下是有意义的。一个常见的场景是当单个用户(例如移动连接)使用 RSocket 连接时。在这种情况下,只有单个用户使用该连接,因此可以在连接时进行一次身份验证。

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

在某些场景中,设置时和每次请求时都进行身份验证是有意义的。考虑前面描述的 web 应用程序。如果我们需要将连接限制在 web 应用程序本身,我们可以在连接时提供一个具有 SETUP 权限的凭证。然后每个用户可以有不同的权限,但没有 SETUP 权限。这意味着各个用户可以发出请求,但不能建立额外的连接。

简单认证

备注

基本认证已演变为简单认证,目前仅出于向后兼容的目的而支持。有关设置方法,请参见 RSocketSecurity.basicAuthentication(Customizer)

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

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

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

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

然后你可以在设置中使用它向接收者发送用户名和密码:

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

或者,也可以在请求中发送用户名和密码。

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

JWT

Spring Security 支持 Bearer Token Authentication Metadata Extension。这种支持的形式是验证 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();
}
java

上述配置依赖于存在一个 ReactiveJwtDecoder @Bean。下面是一个从 issuer 创建它的示例:

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

RSocket 发送方不需要做任何特殊操作来发送 token,因为该值是一个简单的 String。以下示例在设置时发送 token:

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

或者,您也可以在请求中发送该令牌:

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

RSocket 授权

RSocket 授权通过 AuthorizationPayloadInterceptor 执行,它充当控制器来调用 ReactiveAuthorizationManager 实例。你可以使用 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
);
java
  • 设置连接需要 ROLE_SETUP 权限。

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

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

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

  • 该规则确保没有现有规则的请求需要用户进行身份验证。请求中包含元数据,但不包括额外的有效载荷。

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

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