取得領先
VMware 提供培訓和認證,以加速您的進度。
了解更多嗨,Spring 愛好者們! 在 Spring 小技巧 第七季 的第一集中,我們將探討如何使用 Spring Security 來鎖定 RSocket 服務。
嗨,Spring 愛好者們! 在這一集中,我們將探討如何一起使用 Spring Security 和 RSocket。 RSocket 是 Netflix 和 Facebook 工程師開發的一種與 payload 和平台無關的線路協定,它支援線路上的 Reactive Streams 概念。 該協定是一種以有狀態連線為中心的協定:請求者節點連線並保持連線到另一個回應者節點。 連線後,任何一方都可以隨時傳輸資訊。 連線是多路複用的,這意味著一個連線可以處理多個請求。 RSocket 從一開始就被設計為支援傳播帶外資訊,例如標頭和服務健康資訊,以及 payload 本身。 因此,一個使用者可以使用與一個服務的連線,或者多個使用者可以使用相同的連線。
在這個影片中,我們以 Spring Framework 5.2 的核心 RSocket 支援(以及非常方便的 @MessageMapping
元件模型)為基礎,建立一個 RSocket 用戶端,然後以安全的方式連線到 RSocket 服務。
讓我們介紹一個基本的 RSocket 服務。 您需要前往 Spring Initializr 並使用選定的 RSocket 和 Security 產生一個新專案,並且 - 重要的是 - Spring Boot 2.3 或更高版本。
package com.example.greetingsservice;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity;
import org.springframework.security.config.annotation.rsocket.RSocketSecurity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.rsocket.core.PayloadSocketAcceptorInterceptor;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.security.Principal;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Supplier;
import java.util.stream.Stream;
@SpringBootApplication
public class GreetingsServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GreetingsServiceApplication.class, args);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class GreetingResponse {
private String message;
}
@Controller
class GreetingController {
@MessageMapping("greetings")
Flux<GreetingResponse> greet(@AuthenticationPrincipal Mono<UserDetails> user) {
return user.map(UserDetails::getUsername).flatMapMany(GreetingController::greet);
}
private static Flux<GreetingResponse> greet(String name) {
return Flux.fromStream(
Stream
.generate(() -> new GreetingResponse("Hello " + name + " @ " + Instant.now().toString())))
.delayElements(Duration.ofSeconds(1));
}
}
我還製作了另外兩個關於 RSocket 和 Spring 對 RSocket 的支援 的影片,您可以在觀看這個影片之前參考它們。 第一個介紹了原始 RSocket API,第二個介紹了 Spring 中的元件模型。 請參考這些內容以了解控制器中發生的事情。
Spring Security 提供了三種機制來保護基於 RSocket 的服務。 BASIC 驗證有點像 HTTP BASIC - 它支援使用者名稱和密碼。 它現在也被棄用了。 因此,我們將在本影片中重點介紹簡單驗證。 簡單驗證也是基於使用者名稱和密碼的。 RSocket 也支援基於 JWT 的驗證。 JWT 支援基於 Token 的驗證,對於複雜的安全用例來說,它可能更有趣。 (基於 RSocket 的 JWT 驗證可能將是另一個影片的主題。)
由於 RSocket 連線可以是有狀態且共享的,因此我們需要決定:我們是在建立連線時進行驗證,還是針對透過連線傳送的每條訊息進行驗證? 如果它是共享的,我們將希望每個使用者為每個請求提供自己的驗證。
Spring Security 解決了兩個問題:驗證和授權。 這些是相關但正交的問題。 驗證回答了問題:誰在向系統發出請求? 授權回答了問題:一旦他們進入系統,他們被允許做什麼?
讓我們介紹應用程式的 Spring Security 配置。
@Configuration
@EnableRSocketSecurity
class RSocketSecurityConfiguration {
@Bean
RSocketMessageHandler messageHandler(RSocketStrategies strategies) {
var mh = new RSocketMessageHandler();
mh.getArgumentResolverConfigurer().addCustomResolver(new AuthenticationPrincipalArgumentResolver());
mh.setRSocketStrategies(strategies);
return mh;
}
@Bean
MapReactiveUserDetailsService authentication() {
var jlong = User.withDefaultPasswordEncoder().username("jlong").password("pw").roles("USER").build();
var rwinch = User.withDefaultPasswordEncoder().username("rwinch").password("pw").roles("ADMIN", "USER").build();
return new MapReactiveUserDetailsService(jlong, rwinch);
}
@Bean
PayloadSocketAcceptorInterceptor authorization(RSocketSecurity security) {
return security
.authorizePayload(spec ->
spec
.route("greetings").authenticated()
.anyExchange().permitAll()
)
.simpleAuthentication(Customizer.withDefaults())
.build();
}
}
安全配置包含三個 bean。 第一個是 messageHandler
,它啟動 Spring Security 元件模型的部分,讓我們將經過驗證的使用者(使用 @AuthenticatedPrincipal
註解)注入到我們的處理程式方法(那些使用 @MessageMapping
註解的方法)中。
第二個 bean,authentication
,安裝了一個簡單的使用者名稱和密碼字典。 您可以與任意數量的不同身分提供者進行通訊,但為了便於示範,我配置了一個記憶體中的 MapReactiveUserDetailsService
。
第三個 bean,authorization
,至少在我看來是最有趣的。 這個 bean 的目標是告訴框架哪些 RSocket 路由(在本例中是 greetings
)可以被請求存取。 希望這是自我描述的:對 greetings
的所有請求都應該經過驗證。 否則,任何其他請求都允許未經檢查地通過。
現在我們已經啟動並運行了它,讓我們看看客戶端。
package com.example.greetingsclient;
import io.rsocket.metadata.WellKnownMimeType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.rsocket.messaging.RSocketStrategiesCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.rsocket.metadata.SimpleAuthenticationEncoder;
import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import reactor.core.publisher.Mono;
@Log4j2
@SpringBootApplication
public class GreetingsClientApplication {
private final MimeType mimeType = MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
private final UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("jlong", "pw");
@SneakyThrows
public static void main(String[] args) {
SpringApplication.run(GreetingsClientApplication.class, args);
System.in.read();
}
@Bean
RSocketStrategiesCustomizer rSocketStrategiesCustomizer() {
return strategies -> strategies.encoder(new SimpleAuthenticationEncoder());
}
@Bean
RSocketRequester rSocketRequester(RSocketRequester.Builder builder) {
return builder
// .setupMetadata(this.credentials , this.mimeType)
.connectTcp("localhost", 8888)
.block();
}
@Bean
ApplicationListener<ApplicationReadyEvent> ready(RSocketRequester greetings) {
return event ->
greetings
.route("greetings")
.metadata(this.credentials, this.mimeType)
.data(Mono.empty())
.retrieveFlux(GreetingResponse.class)
.subscribe(gr -> log.info("secured response: " + gr.toString()));
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class GreetingResponse {
private String message;
}
我們將向服務傳送元數據。 我們有兩個選擇。 如果與 RSocket 連線的連線是共享的,那麼我們希望為每個請求傳送元數據。 這就是我們在這個範例中所做的,因為這是更可能發生的情況。 另一方面,如果您只需要驗證一次,那麼您可以在 rSocketRequester
bean 中建立連線時傳送元數據。
我們在事件監聽器中使用 RSocketRequester
客戶端,在其中我們呼叫服務上的 greetings
路由。 它基本上和以前一樣,略有不同的是我們正在請求中編碼元數據以進行驗證。
我們才剛開始觸及這個部落格中的服務表面 - 請觀看影片以了解更多詳細資訊! :D