此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.1spring-doc.cn

RSocket 安全性

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

您可以找到一些演示以下代码的示例应用程序: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 基础设施连接起来。 在 Spring Boot 应用程序中,这是使用以下代码自动完成的。SecuritySocketAcceptorInterceptorServerRSocketFactoryPayloadSocketAcceptorInterceptorRSocketSecurityAutoConfigurationspring-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)
        }
    }
}

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

简单身份验证

Basic Authentication 草稿演变为 Simple Authentication,并且仅支持向后兼容。 请参阅设置。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 支持 Bearer Token Authentication Metadata Extension。 支持的形式包括对 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 发送者不需要做任何特殊的事情来发送令牌,因为该值只是一个简单的 String。 例如,可以在设置时发送令牌:spring-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>()
    }
}

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

请务必了解授权规则是按顺序执行的。 将仅调用匹配的第一个授权规则。spring-doc.cn