对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

Spring Security 的 RSocket 支持依赖于 . 安全性的主要切入点位于 它调整 RSocket API 以允许拦截 with 实现。SocketAcceptorInterceptorPayloadSocketAcceptorInterceptorPayloadExchangePayloadInterceptorSpring中文文档

您可以找到一些演示以下代码的示例应用程序:Spring中文文档

最低 RSocket 安全配置

您可以在下面找到最低限度的 RSocket 安全配置:Spring中文文档

@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 设置为要求任何请求都经过身份验证的用户。Spring中文文档

添加 SecuritySocketAcceptorInterceptor

要使 Spring Security 正常工作,我们需要将 . 这就是将我们创建的 RSocket 基础设施连接起来的原因。SecuritySocketAcceptorInterceptorServerRSocketFactoryPayloadSocketAcceptorInterceptorSpring中文文档

Spring Boot 会在您包含正确的依赖项时自动注册它。RSocketSecurityAutoConfigurationSpring中文文档

或者,如果您没有使用 Boot 的自动配置,则可以通过以下方式手动注册它:Spring中文文档

@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)
        }
    }
}

要自定义侦听器本身,请使用添加身份验证授权RSocketSecuritySpring中文文档

RSocket 身份验证

执行 RSocket 身份验证,该身份验证充当调用实例的控制器。AuthenticationPayloadInterceptorReactiveAuthenticationManagerSpring中文文档

设置时与请求时的身份验证

通常,身份验证可以在设置时和/或请求时进行。Spring中文文档

在一些情况下,安装时的身份验证是有意义的。 一种常见的情况是,当单个用户(即移动连接)利用 RSocket 连接时。 在这种情况下,只有一个用户利用连接,因此可以在连接时执行一次身份验证。Spring中文文档

在共享 RSocket 连接的情况下,在每个请求上发送凭据是有意义的。 例如,作为下游服务连接到 RSocket 服务器的 Web 应用程序将建立所有用户都利用的单个连接。 在这种情况下,如果 RSocket 服务器需要根据 Web 应用程序的用户凭据执行授权,则每个请求的凭据是有意义的。Spring中文文档

在某些情况下,在设置时和每个请求时进行身份验证是有意义的。 请考虑前面所述的 Web 应用程序。 如果我们需要限制与 Web 应用程序本身的连接,我们可以在连接时提供具有授权的凭据。 然后,每个用户将拥有不同的权限,但没有权限。 这意味着单个用户可以发出请求,但不能建立其他连接。SETUPSETUPSpring中文文档

简单身份验证

基本身份验证草稿演变为简单身份验证,并且仅支持向后兼容。 请参阅设置。RSocketSecurity.basicAuthentication(Customizer)Spring中文文档

RSocket 接收器可以使用使用 DSL 部分自动设置的凭据进行解码。 可以在下面找到显式配置。AuthenticationPayloadExchangeConvertersimpleAuthenticationSpring中文文档

@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 的 .SimpleAuthenticationEncoderRSocketStrategiesSpring中文文档

RSocketStrategies.Builder strategies = ...;
strategies.encoder(new SimpleAuthenticationEncoder());
var strategies: RSocketStrategies.Builder = ...
strategies.encoder(SimpleAuthenticationEncoder())

然后,它可用于在设置中向接收方发送用户名和密码:Spring中文文档

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)

或者,也可以另外在请求中发送用户名和密码。Spring中文文档

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>()
    }
}

智威汤逊

Spring Security 支持持有者令牌身份验证元数据扩展。 支持的形式是验证 JWT(确定 JWT 有效),然后使用 JWT 做出授权决策。Spring中文文档

RSocket 接收器可以使用使用 DSL 部分自动设置的凭据进行解码。 可以在下面找到示例配置:BearerPayloadExchangeConverterjwtSpring中文文档

@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@BeanSpring中文文档

@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。 例如,可以在设置时发送令牌:Spring中文文档

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)

或者,也可以另外在请求中发送令牌。Spring中文文档

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>()
    }
}

基本身份验证草稿演变为简单身份验证,并且仅支持向后兼容。 请参阅设置。RSocketSecurity.basicAuthentication(Customizer)Spring中文文档

RSocket 授权

执行 RSocket 授权,该授权充当调用实例的控制器。 DSL 可用于设置基于 . 可以在下面找到示例配置:AuthorizationPayloadInterceptorReactiveAuthorizationManagerPayloadExchangeSpring中文文档

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 此规则利用自定义授权。 匹配器表示一个变量,其名称在 . 自定义授权规则在方法中公开。usernamecontextcheckFriends
5 此规则可确保尚没有规则的请求将要求对用户进行身份验证。 请求是包含元数据的位置。 它不包括额外的有效载荷。
6 此规则确保任何人都允许任何尚未规则的交换。 在此示例中,这意味着没有元数据的有效负载没有授权规则。

请务必了解授权规则是按顺序执行的。 仅调用匹配的第一个授权规则。Spring中文文档

1 设置连接需要权限ROLE_SETUP
2 如果路由是授权,则仅要求对用户进行身份验证fetch.profile.me
3 在此规则中,我们设置了一个自定义匹配器,其中授权要求用户具有权限ROLE_CUSTOM
4 此规则利用自定义授权。 匹配器表示一个变量,其名称在 . 自定义授权规则在方法中公开。usernamecontextcheckFriends
5 此规则可确保尚没有规则的请求将要求对用户进行身份验证。 请求是包含元数据的位置。 它不包括额外的有效载荷。
6 此规则确保任何人都允许任何尚未规则的交换。 在此示例中,这意味着没有元数据的有效负载没有授权规则。