|
对于最新的稳定版本,请使用 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这是使用simpleAuthenticationDSL 的一部分。
可以在下面找到显式配置。
-
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这是使用jwtDSL 的一部分。
示例配置如下:
-
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 | 此规则确保任何人都允许任何尚未有规则的交换。 在此示例中,这意味着没有元数据的有效负载没有授权规则。 |
请务必了解授权规则是按顺序执行的。 仅调用第一个匹配的授权规则。