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.