RSocket 安全
Spring Security 的 RSocket 支援依賴於一個 SocketAcceptorInterceptor。安全的主要入口點是 PayloadSocketAcceptorInterceptor,它適配 RSocket API,以允許使用 PayloadInterceptor 實現來攔截 PayloadExchange。
以下示例展示了一個最小的 RSocket Security 配置
-
Hello RSocket hellorsocket
最小 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)
}
}
}
RSocket 認證
RSocket 認證透過 AuthenticationPayloadInterceptor 執行,它充當控制器來呼叫 ReactiveAuthenticationManager 例項。
設定時與請求時認證
通常,認證可以在設定時或請求時或兩者兼有。
設定時認證在某些場景下有意義。一個常見的場景是單個使用者(例如移動連線)使用 RSocket 連線。在這種情況下,只有一個使用者使用連線,因此認證可以在連線時一次完成。
在 RSocket 連線共享的場景中,每次請求都發送憑據是合理的。例如,一個作為下游服務連線到 RSocket 伺服器的 Web 應用程式會建立一個所有使用者共享的單個連線。在這種情況下,如果 RSocket 伺服器需要根據 Web 應用程式的使用者憑據執行授權,那麼對每個請求進行認證是合理的。
在某些場景中,設定時和每個請求都進行認證是有意義的。考慮如前所述的 Web 應用程式。如果我們需要限制 Web 應用程式本身的連線,我們可以在連線時提供具有 SETUP 許可權的憑據。然後每個使用者可以擁有不同的許可權,但不能擁有 SETUP 許可權。這意味著單個使用者可以發出請求,但不能建立額外的連線。
簡單認證
Spring Security 支援 簡單認證元資料擴充套件。
|
基本認證演變為簡單認證,僅支援向後相容。有關設定,請參見 |
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 | 此規則確保任何尚未有規則的交換都允許任何人訪問。在此示例中,這意味著沒有元資料的有效載荷也沒有授權規則。 |
請注意,授權規則按順序執行。只有第一個匹配的授權規則才會被呼叫。