此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.1! |
RSocket 安全性
Spring Security 的 RSocket 支持依赖于 .
安全性的主要入口点位于 其中,它调整了 RSocket API 以允许拦截 with 实现。SocketAcceptorInterceptor
PayloadSocketAcceptorInterceptor
PayloadExchange
PayloadInterceptor
您可以找到一些演示以下代码的示例应用程序:
-
你好 RSocket hellorsocket
最小 RSocket 安全配置
您可以在下面找到最小的 RSocket Security 配置:
-
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 正常工作,我们需要将 .
这就是将我们创建的原因与 RSocket 基础设施连接起来。SecuritySocketAcceptorInterceptor
ServerRSocketFactory
PayloadSocketAcceptorInterceptor
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 支持简单身份验证元数据扩展。
Basic Authentication 草稿演变为 Simple Authentication,并且仅支持向后兼容。
请参阅设置。 |
RSocket 接收器可以解码凭据,该凭据是使用 DSL 的一部分自动设置的。
可以在下面找到显式配置。AuthenticationPayloadExchangeConverter
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 发送者可以发送凭据,使用该凭据可以添加到 Spring 的 .SimpleAuthenticationEncoder
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 接收器可以解码凭据,该凭据是使用 DSL 的一部分自动设置的。
可以在下面找到示例配置:BearerPayloadExchangeConverter
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()
}
上面的配置依赖于存在的存在。
下面提供了从颁发者创建一个 ID 的示例: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")
}
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 授权,它充当调用实例的控制器。
DSL 可用于根据 .
可以在下面找到示例配置:AuthorizationPayloadInterceptor
ReactiveAuthorizationManager
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 | 如果路由为 authorization,则仅要求对用户进行身份验证fetch.profile.me |
3 | 在此规则中,我们设置了一个自定义匹配器,其中 authorization 要求用户具有权限ROLE_CUSTOM |
4 | 此规则利用自定义授权。
匹配器表示一个变量,其名称在 .
该方法中公开了自定义授权规则。username context checkFriends |
5 | 此规则可确保尚无规则的请求将要求对用户进行身份验证。 请求是包含元数据的位置。 它不会包含额外的负载。 |
6 | 此规则确保任何人都允许任何尚未制定规则的交易所。 在此示例中,这意味着没有元数据的负载没有授权规则。 |
请务必了解授权规则是按顺序执行的。 将仅调用匹配的第一个授权规则。