# RSocket 安全性
Spring Security 的 RSocket 支持依赖于SocketAcceptorInterceptor
。进入安全的主要入口点是PayloadSocketAcceptorInterceptor
,它调整了 RSocket API,允许使用PayloadInterceptor
实现来拦截PayloadExchange
。
你可以找到几个示例应用程序来演示下面的代码:
# 最小的 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);
}
}
Kotlin
@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-授权以要求对任何请求进行身份验证的用户。
# 添加安全性:接收截取程序
为了使安全性工作,我们需要将SecuritySocketAcceptorInterceptor
应用到ServerRSocketFactory
。这就是将我们创建的PayloadSocketAcceptorInterceptor
与 RSocket 基础架构连接起来的原因。在 Spring 引导应用程序中,这是使用RSocketSecurityAutoConfiguration
和以下代码自动完成的。
@Bean
RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInterceptor interceptor) {
return (server) -> server.interceptors((registry) -> registry.forSocketAcceptor(interceptor));
}
# RSocket 身份验证
RSocket 身份验证是通过AuthenticationPayloadInterceptor
执行的,该控制器充当调用ReactiveAuthenticationManager
实例的控制器。
# 设置时的身份验证与请求时的身份验证
通常,身份验证可以在设置时间和/或请求时间发生。
在几个场景中,设置时的身份验证是有意义的。一个常见的场景是当单个用户(即移动连接)利用 RSocket 连接时。在这种情况下,只有单个用户在利用连接,因此可以在连接时进行一次身份验证。
在共享 RSocket 连接的场景中,对每个请求发送凭据是有意义的。例如,作为下游服务连接到 RSocket 服务器的 Web 应用程序将生成一个所有用户都可以利用的单个连接。在这种情况下,如果 RSocket 服务器需要根据每个请求的 Web 应用程序的用户凭据来执行授权,这是有意义的。
在某些情况下,在设置和每个请求中进行身份验证是有意义的。考虑如前所述的 Web 应用程序。如果我们需要将连接限制到 Web 应用程序本身,那么我们可以在连接时提供一个具有SETUP
权限的凭据。然后,每个用户将拥有不同的权限,但不是SETUP
权限。这意味着个人用户可以提出请求,但不能建立额外的连接。
# 简单的身份验证
Spring 安全性支持简单的身份验证元数据扩展 (opens new window)。
基本的身份验证草案演变成简单的身份验证,并且只支持向后兼容性。 设置它的方法请参见 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();
}
Kotlin
@Bean
open fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
rsocket
.authorizePayload { authorize -> authorize
.anyRequest().authenticated()
.anyExchange().permitAll()
}
.simpleAuthentication(withDefaults())
return rsocket.build()
}
RSocket 发送者可以使用SimpleAuthenticationEncoder
发送凭据,该凭据可以添加到 Spring 的RSocketStrategies
中。
爪哇
RSocketStrategies.Builder strategies = ...;
strategies.encoder(new SimpleAuthenticationEncoder());
Kotlin
var strategies: RSocketStrategies.Builder = ...
strategies.encoder(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);
Kotlin
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)
另外,也可以在请求中发送用户名和密码。
爪哇
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)
);
}
Kotlin
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 安全性支持承载令牌身份验证元数据扩展 (opens new window)。支持的形式是对 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();
}
Kotlin
@Bean
fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
rsocket
.authorizePayload { authorize -> authorize
.anyRequest().authenticated()
.anyExchange().permitAll()
}
.jwt(withDefaults())
return rsocket.build()
}
上述配置依赖于存在ReactiveJwtDecoder``@Bean
。可以在下面找到从发行者创建一个发行者的示例:
爪哇
@Bean
ReactiveJwtDecoder jwtDecoder() {
return ReactiveJwtDecoders
.fromIssuerLocation("https://example.com/auth/realms/demo");
}
Kotlin
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
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);
Kotlin
val authenticationMimeType: MimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
val token: BearerTokenMetadata = ...
val 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)
);
}
Kotlin
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
设置授权规则。下面是一个配置示例:
爪哇
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)
);
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)
1 | 建立连接需要授权ROLE_SETUP |
---|---|
2 | 如果路由是fetch.profile.me ,则授权只需要对用户进行身份验证即可。 |
3 | 在这个规则中,我们设置了一个自定义的匹配器,其中授权要求用户拥有ROLE_CUSTOM 的权限 |
4 | 该规则利用自定义授权。 Matcher 表示一个名为 username 的变量,该变量在context 中可用。在 checkFriends 方法中公开了一个自定义授权规则。 |
5 | 此规则确保尚未具有规则的请求将需要对用户进行身份验证。 包含元数据的位置是一个请求。 它将不包括额外的有效负载。 |
6 | 此规则确保任何人都可以使用任何尚未拥有规则的交换。 在本例中,这意味着没有元数据的有效负载没有授权规则。 |
重要的是要理解授权规则是按顺序执行的。将只调用匹配的第一个授权规则。