RSocket 安全

Spring Security 的 RSocket 支持依赖于一个 SocketAcceptorInterceptor。安全的主要入口点是 PayloadSocketAcceptorInterceptor,它适配 RSocket API,以允许使用 PayloadInterceptor 实现来拦截 PayloadExchange

以下示例展示了一个最小的 RSocket Security 配置

最小 RSocket 安全配置

您可以在下面找到一个最小的 RSocket Security 配置

  • 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 中自动注册它。

或者,如果您不使用 Boot 的自动配置,可以按以下方式手动注册它

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

要自定义拦截器本身,请使用 RSocketSecurity 添加认证授权

RSocket 认证

RSocket 认证通过 AuthenticationPayloadInterceptor 执行,它充当控制器来调用 ReactiveAuthenticationManager 实例。

设置时与请求时认证

通常,认证可以在设置时或请求时或两者兼有。

设置时认证在某些场景下有意义。一个常见的场景是单个用户(例如移动连接)使用 RSocket 连接。在这种情况下,只有一个用户使用连接,因此认证可以在连接时一次完成。

在 RSocket 连接共享的场景中,每次请求都发送凭据是合理的。例如,一个作为下游服务连接到 RSocket 服务器的 Web 应用程序会建立一个所有用户共享的单个连接。在这种情况下,如果 RSocket 服务器需要根据 Web 应用程序的用户凭据执行授权,那么对每个请求进行认证是合理的。

在某些场景中,设置时和每个请求都进行认证是有意义的。考虑如前所述的 Web 应用程序。如果我们需要限制 Web 应用程序本身的连接,我们可以在连接时提供具有 SETUP 权限的凭据。然后每个用户可以拥有不同的权限,但不能拥有 SETUP 权限。这意味着单个用户可以发出请求,但不能建立额外的连接。

简单认证

Spring Security 支持 简单认证元数据扩展

基本认证演变为简单认证,仅支持向后兼容。有关设置,请参见 RSocketSecurity.basicAuthentication(Customizer)

RSocket 接收器可以通过使用 AuthenticationPayloadExchangeConverter 解码凭据,该凭据通过 DSL 的 simpleAuthentication 部分自动设置。以下示例显示了一个显式配置

  • 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 支持 Bearer Token 认证元数据扩展。这种支持的形式是认证 JWT(确定 JWT 有效),然后使用 JWT 进行授权决策。

RSocket 接收器可以通过使用 BearerPayloadExchangeConverter 解码凭据,该凭据通过 DSL 的 jwt 部分自动设置。以下列表显示了一个示例配置

  • 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 发送方无需执行任何特殊操作即可发送令牌,因为该值是一个简单的 String。以下示例在设置时发送令牌

  • 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)

或者,您可以在请求中发送令牌

  • 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) -> payloadExchange(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 此规则确保任何尚未有规则的交换都允许任何人访问。在此示例中,这意味着没有元数据的有效载荷也没有授权规则。

请注意,授权规则按顺序执行。只有第一个匹配的授权规则才会被调用。

© . This site is unofficial and not affiliated with VMware.