RSocket 安全
Spring Security 的 RSocket 支持依赖于 SocketAcceptorInterceptor
。安全性的主要入口位于 PayloadSocketAcceptorInterceptor
中,它调整 RSocket API 以允许使用 PayloadInterceptor
实现拦截 PayloadExchange
。
以下示例展示了最简单的 RSocket 安全配置
-
Hello RSocket hellorsocket
最简单的 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
中自动注册它。
或者,如果您不使用 Boot 的自动配置,则可以按照以下方式手动注册它
-
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 身份验证使用 AuthenticationPayloadInterceptor
执行,该拦截器充当一个控制器,用于调用 ReactiveAuthenticationManager
实例。
设置时间与请求时间身份验证
通常,身份验证可以在设置时间或请求时间或两者同时发生。
在设置时间进行身份验证在少数场景中是有意义的。一个常见场景是单个用户(例如移动连接)使用 RSocket 连接。在这种情况下,只有一个用户使用该连接,因此可以在连接时进行一次身份验证。
在 RSocket 连接被共享的场景中,在每个请求上发送凭证是有意义的。例如,作为下游服务连接到 RSocket 服务器的 Web 应用程序将建立所有用户都使用的单个连接。在这种情况下,如果 RSocket 服务器需要根据 Web 应用程序用户的凭证执行授权,则对每个请求进行身份验证是有意义的。
在某些情况下,在设置和每个请求中进行身份验证是有意义的。考虑如前所述的 Web 应用程序。如果我们需要将连接限制到 Web 应用程序本身,我们可以在连接时使用具有 SETUP
权限的凭证。然后,每个用户可以具有不同的权限,但不是 SETUP
权限。这意味着各个用户可以提出请求,但不能建立其他连接。
简单身份验证
Spring Security 支持简单身份验证元数据扩展。
基本身份验证演变为简单身份验证,并且仅支持向后兼容性。请参阅 |
RSocket 接收器可以使用 AuthenticationPayloadExchangeConverter
解码凭证,该转换器通过使用 DSL 的 simpleAuthentication
部分自动设置。以下示例显示了显式配置
-
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 发送器可以使用 SimpleAuthenticationEncoder
发送凭证,您可以将其添加到 Spring 的 RSocketStrategies
中。
-
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 支持 Bearer 令牌身份验证元数据扩展。支持以验证 JWT(确定 JWT 有效)的形式出现,然后使用 JWT 做出授权决策。
RSocket 接收器可以使用 BearerPayloadExchangeConverter
解码凭据,该转换器通过使用 DSL 的 jwt
部分自动设置。以下列表显示了一个示例配置
-
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
。以下是可以从颁发者处创建该 @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 发送器无需执行任何特殊操作即可发送令牌,因为该值是一个简单的 String
。以下示例在设置时发送令牌
-
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 授权使用 AuthorizationPayloadInterceptor
执行,该拦截器充当控制器以调用 ReactiveAuthorizationManager
实例。您可以使用 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 | 此规则确保尚未有规则的任何交换对任何人都是允许的。在此示例中,这意味着没有元数据的有效负载也没有授权规则。 |
请注意,授权规则按顺序执行。仅调用第一个匹配的授权规则。