|
此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4! |
RSocket 安全
Spring Security 的 RSocket 支持依赖于一个 SocketAcceptorInterceptor。
安全的主要入口点位于 PayloadSocketAcceptorInterceptor,它将 RSocket API 调整为允许使用 PayloadExchange 实现拦截 PayloadInterceptor。
以下示例展示了最小的RSocket 安全配置:
-
你好 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)
}
}
此配置启用简单身份验证,并设置
添加 SecuritySocketAcceptorInterceptor
要使Spring Security生效,我们需要将SecuritySocketAcceptorInterceptor应用到ServerRSocketFactory。
这样做会将我们的PayloadSocketAcceptorInterceptor与RSocket基础设施连接起来。
Spring Boot 会在您包含正确依赖项时,自动在 https://github.com/spring-projects/spring-security-samples/tree/main/reactive/rsocket/hello-security/build.gradle 中注册它。
如果您没有使用 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 支持简单身份验证元数据扩展。
|
基本认证演进为简单认证,仅支持兼容性回退。
请参见 |
Spring框架中的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()
}
The 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 Token Authentication Metadata Extension 提供了支持。 这种支持表现为验证 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。
可以从发行者创建一个示例如下所示:
-
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")
}
The 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)
可以将Tokens在请求中发送:
-
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) -> payloadExchange(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 | 此规则确保任何没有现有规则的交换允许任何人。 在这个例子中,这意味着没有元数据的消息也没有授权规则。 |
注意授权规则是按顺序执行的。 只有第一个匹配的授权规则会被调用。