Spring Security 的 RSocket 支持依赖于 . 安全性的主要入口点在 中,它调整了 RSocket API 以允许拦截 with 实现。SocketAcceptorInterceptorPayloadSocketAcceptorInterceptorPayloadExchangePayloadInterceptorspring-doc.cn

以下示例显示了最小的 RSocket Security 配置:spring-doc.cn

最小 RSocket 安全配置

您可以在下面找到最小的 RSocket Security 配置:spring-doc.cn

@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-doc.cn

添加 SecuritySocketAcceptorInterceptor

要使 Spring Security 正常工作,我们需要将 . 这样做将我们与 RSocket 基础设施连接起来。SecuritySocketAcceptorInterceptorServerRSocketFactoryPayloadSocketAcceptorInterceptorspring-doc.cn

Spring 当你包含正确的依赖项时,Boot 会自动将其注册进来。RSocketSecurityAutoConfigurationspring-doc.cn

或者,如果您不使用 Boot 的自动配置,您可以通过以下方式手动注册它:spring-doc.cn

@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-doc.cn

RSocket 身份验证

RSocket 身份验证是使用 执行的,它充当调用实例的控制器。AuthenticationPayloadInterceptorReactiveAuthenticationManagerspring-doc.cn

设置时与请求时进行身份验证

通常,身份验证可以在设置时或请求时进行,或者同时在两者中进行。spring-doc.cn

在少数情况下,设置时的身份验证是有意义的。 一种常见情况是单个用户(例如移动连接)使用 RSocket 连接。 在这种情况下,只有一个用户使用该连接,因此可以在连接时执行一次身份验证。spring-doc.cn

在共享 RSocket 连接的情况下,在每个请求上发送凭据是有意义的。 例如,作为下游服务连接到 RSocket 服务器的 Web 应用程序将建立所有用户使用的单个连接。 在这种情况下,如果 RSocket 服务器需要根据 Web 应用程序的用户凭证执行授权,则每个请求的身份验证都是有意义的。spring-doc.cn

在某些情况下,在设置和每个请求时进行身份验证都是有意义的。 考虑一个 Web 应用程序,如前所述。 如果我们需要将连接限制为 Web 应用程序本身,我们可以在连接时提供具有权限的凭证。 然后,每个用户可以拥有不同的权限,但不能拥有不同的权限。 这意味着单个用户可以发出请求,但不能进行其他连接。SETUPSETUPspring-doc.cn

简单身份验证

基本身份验证演变为简单身份验证,仅支持向后兼容。 请参阅设置。RSocketSecurity.basicAuthentication(Customizer)spring-doc.cn

RSocket 接收器可以使用 对凭据进行解码,这是使用 DSL 部分自动设置的。 以下示例显示了显式配置:AuthenticationPayloadExchangeConvertersimpleAuthenticationspring-doc.cn

@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-doc.cn

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

然后,您可以使用它向设置中的接收者发送用户名和密码:spring-doc.cn

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-doc.cn

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 支持不记名令牌身份验证元数据扩展。 支持的形式包括对 JWT 进行身份验证(确定 JWT 有效),然后使用 JWT 做出授权决策。spring-doc.cn

RSocket 接收器可以使用 对凭据进行解码,这是使用 DSL 部分自动设置的。 下面的清单显示了一个示例配置:BearerPayloadExchangeConverterjwtspring-doc.cn

@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@Beanspring-doc.cn

@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 发送者不需要执行任何特殊操作来发送令牌,因为该值是一个简单的 . 以下示例在设置时发送令牌:Stringspring-doc.cn

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-doc.cn

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-doc.cn

RSocket 授权

RSocket 授权是使用 执行的,它充当调用实例的控制器。 您可以使用 DSL 根据 设置授权规则。 下面的清单显示了一个示例配置:AuthorizationPayloadInterceptorReactiveAuthorizationManagerPayloadExchangespring-doc.cn

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 在此规则中,我们设置了一个自定义匹配器,其中 authorization 要求用户具有权限。ROLE_CUSTOM
4 此规则使用自定义授权。 匹配器表示一个名称为 的变量,该变量在 . 该方法中公开了自定义授权规则。usernamecontextcheckFriends
5 此规则可确保尚无规则的请求要求对用户进行身份验证。 请求是包含元数据的位置。 它不会包含额外的负载。
6 此规则确保任何人都允许任何尚未制定规则的交易所。 在此示例中,这意味着没有元数据的负载也没有授权规则。

请注意,授权规则是按顺序执行的。 仅调用匹配的第一个授权规则。spring-doc.cn

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