对于最新的稳定版本,请使用 Spring Security 6.5.3! |
RSocket 安全
Spring Security 的 RSocket 支持依赖于SocketAcceptorInterceptor
.
安全的主要入口点位于PayloadSocketAcceptorInterceptor
它调整了 RSocket API 以允许拦截PayloadExchange
跟PayloadInterceptor
实现。
您可以在下面找到一些演示代码的示例应用程序:
-
你好 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
使用以下代码。
-
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
这是使用simpleAuthentication
DSL 的一部分。
可以在下面找到显式配置。
-
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 支持持有者Tokens身份验证元数据扩展。 支持的形式是对 JWT 进行身份验证(确定 JWT 是否有效),然后使用 JWT 做出授权决策。
RSocket 接收器可以使用BearerPayloadExchangeConverter
这是使用jwt
DSL 的一部分。
示例配置如下:
-
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")
}
RSocket 发送者不需要执行任何特殊作来发送Tokens,因为该值只是一个简单的 String。 例如,可以在设置时发送Tokens:
-
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 -> 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 | 此规则确保任何人都允许任何尚未有规则的交换。 在此示例中,这意味着没有元数据的有效负载没有授权规则。 |
请务必了解授权规则是按顺序执行的。 仅调用第一个匹配的授权规则。