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

RSocket 安全性

Spring Security 的 RSocket 支持依赖于SocketAcceptorInterceptor. 安全的主要入口点是PayloadSocketAcceptorInterceptor,它调整 RSocket API 以允许拦截PayloadExchangePayloadInterceptor实现。spring-doc.cadn.net.cn

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

最小 RSocket 安全配置

您可以在下面找到最小的 RSocket Security 配置:spring-doc.cadn.net.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.cadn.net.cn

添加 SecuritySocketAcceptorInterceptor

要使 Spring Security 正常工作,我们需要申请SecuritySocketAcceptorInterceptorServerRSocketFactory. 这样做将我们的PayloadSocketAcceptorInterceptor使用 RSocket 基础架构。spring-doc.cadn.net.cn

Spring Boot 会自动在RSocketSecurityAutoConfiguration当您包含正确的依赖项时。spring-doc.cadn.net.cn

或者,如果您不使用 Boot 的自动配置,您可以通过以下方式手动注册它:spring-doc.cadn.net.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)
        }
    }
}

要自定义拦截器本身,请使用RSocketSecurity以添加身份验证授权spring-doc.cadn.net.cn

RSocket 身份验证

RSocket 身份验证是使用AuthenticationPayloadInterceptor,它充当控制器来调用ReactiveAuthenticationManager实例。spring-doc.cadn.net.cn

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

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

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

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

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

简单身份验证

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

RSocket 接收器可以使用AuthenticationPayloadExchangeConverter,该 API 是使用simpleAuthentication部分。 以下示例显示了显式配置:spring-doc.cadn.net.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 发送者可以使用SimpleAuthenticationEncoder,您可以将其添加到 Spring 的RSocketStrategies.spring-doc.cadn.net.cn

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

然后,您可以使用它向设置中的接收者发送用户名和密码:spring-doc.cadn.net.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.cadn.net.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 has support for the Bearer Token Authentication Metadata Extension. The support comes in the form of authenticating a JWT (determining that the JWT is valid) and then using the JWT to make authorization decisions.spring-doc.cadn.net.cn

The RSocket receiver can decode the credentials by using BearerPayloadExchangeConverter, which is automatically setup by using the jwt portion of the DSL. The following listing shows an example configuration:spring-doc.cadn.net.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()
}

The configuration above relies on the existence of a ReactiveJwtDecoder @Bean being present. An example of creating one from the issuer can be found below:spring-doc.cadn.net.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")
}

The RSocket sender does not need to do anything special to send the token, because the value is a simple String. The following example sends the token at setup time:spring-doc.cadn.net.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)

Alternatively or additionally, you can send the token in a request:spring-doc.cadn.net.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 Authorization

RSocket authorization is performed with AuthorizationPayloadInterceptor, which acts as a controller to invoke a ReactiveAuthorizationManager instance. You can use the DSL to set up authorization rules based upon the PayloadExchange. The following listing shows an example configuration:spring-doc.cadn.net.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 Setting up a connection requires the ROLE_SETUP authority.
2 If the route is fetch.profile.me, authorization only requires the user to be authenticated.
3 In this rule, we set up a custom matcher, where authorization requires the user to have the ROLE_CUSTOM authority.
4 This rule uses custom authorization. The matcher expresses a variable with a name of username that is made available in the context. A custom authorization rule is exposed in the checkFriends method.
5 This rule ensures that a request that does not already have a rule requires the user to be authenticated. A request is where the metadata is included. It would not include additional payloads.
6 This rule ensures that any exchange that does not already have a rule is allowed for anyone. In this example, it means that payloads that have no metadata also have no authorization rules.

Note that authorization rules are performed in order. Only the first authorization rule that matches is invoked.spring-doc.cadn.net.cn