領先一步
VMware 提供培訓和認證,以加速您的進展。
了解更多閱讀時間:約 6 分鐘 編碼時間:約 20 分鐘
如果您一直在關注我關於 RSocket 的系列文章,您已經學會如何使用 Spring Boot 建構用戶端-伺服器應用程式。在今天的練習中,您將學習如何在 RSocket 應用程式中新增安全性。
當您使用 Spring Security 時,保護 RSocket 應用程式的任務會大大簡化。Spring Security 是任何生產應用程式都必備的模組。它讓您可以輕鬆外掛許多不同的身份驗證提供者,並根據每個使用者的身份及其角色限制他們對應用程式的存取權限。
您將會看到,保護應用程式所需的程式碼非常簡單明瞭。但是,由於安全性是一個非常「跨領域」的考量,因此變更確實會觸及程式碼的幾個不同部分。自行進行這些變更並不困難,但一如既往,完整的程式碼範例可在 GitHub 上取得。
注意: 在撰寫本文時,RSocket 的安全性擴充功能仍在開發中。您可以在此處追蹤其進度。在本練習中,我們將使用簡單驗證,其中包含警告:「簡單驗證會以明文傳輸使用者名稱和密碼。此外,它不會保護與其一起傳輸的酬載的真實性或機密性。這表示使用的傳輸應提供真實性和機密性,以保護使用者名稱和密碼以及相應的酬載。」
在 rsocket-client
和 rsocket-server
專案的 POM.xml
檔案中,新增下列安全性依賴項
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-messaging</artifactId>
</dependency>
這些依賴項將 Spring Security 整合到您的 RSocket 應用程式中。包含 spring-boot-starter-security
套件表示大部分組態都會自動完成。
保護您的 RSocket 回應程式最好分兩個階段進行。首先,新增安全性組態類別,其次,保護您的 RSocket 回應程式方法。
注意:這些變更會暫時中斷您在上一個教學課程中新增的整合測試。別擔心;稍後我會告訴您如何再次修復它。
若要自訂 Spring Security 的組態,請在您的 rsocket-server
專案中,新增一個名為 RSocketSecurityConfig.java
的新類別,其中包含以下程式碼。
注意:import 陳述式遺失了。請在出現提示時要求您的 IDE 為您新增它們。
@Configuration // (1)
@EnableRSocketSecurity // (2)
@EnableReactiveMethodSecurity // (3)
public class RSocketSecurityConfig {
@Bean // (4)
RSocketMessageHandler messageHandler(RSocketStrategies strategies) {
RSocketMessageHandler handler = new RSocketMessageHandler();
handler.getArgumentResolverConfigurer().addCustomResolver(new AuthenticationPrincipalArgumentResolver());
handler.setRSocketStrategies(strategies);
return handler;
}
@Bean // (5)
MapReactiveUserDetailsService authentication() {
//This is NOT intended for production use (it is intended for getting started experience only)
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("pass")
.roles("USER")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("test")
.password("pass")
.roles("NONE")
.build();
return new MapReactiveUserDetailsService(user, admin);
}
@Bean // (6)
PayloadSocketAcceptorInterceptor authorization(RSocketSecurity security) {
security.authorizePayload(authorize ->
authorize
.anyExchange().authenticated() // all connections, exchanges.
).simpleAuthentication(Customizer.withDefaults());
return security.build();
}
指定 @Configuration
(1) 會告知 Spring Boot 這是一個組態類別。@EnableRSocketSecurity
註解 (2) 會啟用 Spring 的 RSocket 安全性功能。設定 @EnableReactiveMethodSecurity
(3) 可讓您保護您的反應式方法。
在 (4) 設定的 RSocketMessageHandler
bean 會自動將使用者憑證轉換為 UserDetails
物件。在 (5) 設定的 MapReactiveUserDetailsService
bean 會向 Spring 提供硬式編碼的使用者資料庫。以這種方式手動提供使用者資料庫不是很實際,但對於此示範來說已足夠。您可以閱讀稍後如何使用其他身份提供者完成此操作。
最後,(6) 的 PayloadSocketAcceptorInterceptor
bean 指定使用者可以對應用程式執行的操作。在這種情況下,使用者必須先進行身份驗證,才能連線或被授予伺服器端功能的存取權限。
使用者的角色決定了他們可以存取的方法。在這種情況下,此「基於角色的存取控制」是使用 Spring Security 的 @PreAuthorize
註解來設定的。以下程式碼顯示了此註解在作用中的範例 — 保護 RSocketController 類別中的「fire-and-forget」訊息對應
@PreAuthorize("hasRole('USER')") // (1)
@MessageMapping("fire-and-forget")
public Mono<Void> fireAndForget(final Message request, @AuthenticationPrincipal UserDetails user) { // (2)
log.info("Received fire-and-forget request: {}", request);
log.info("Fire-And-Forget initiated by '{}' in the role '{}'", user.getUsername(), user.getAuthorities());
return Mono.empty();
}
@PreAuthorize("hasRole('USER')")
註解 (1) 確保只有具有 ‘ROLE_USER’ 權限的使用者才能存取此方法。在上面的 2.1 節中,您建立了一個具有此角色的使用者。
如果您特別眼尖,您會注意到 fireAndForget()
方法簽章中的另外兩個變更。第一個是方法參數現在包含 @AuthenticationPrincipal UserDetails user
(2)。Spring 會自動提供此 user
物件。其次,傳回參數現在是 Mono<Void>
而不是一般的 'void'。需要此變更的原因是 @EnableReactiveMethodSecurity
要求傳回值來自 project Reactor(即 Flux 或 Mono)。
在程式碼範例中,用戶端經歷了幾個程式碼變更。其中大多數與安全性無關。大多數變更只是為了讓用戶端在使用受保護的伺服器端 RSocket 回應程式時更方便使用。在本節中,您只會介紹安全性變更。有關其他程式碼,請參閱程式碼範例。
對用戶端所做的安全性變更都與它如何連線到 RSocket 伺服器有關。連線程式碼已從類別建構子移出,並移至新的 login()
方法中。此登入方法預期使用者在登入時提供其使用者名稱和密碼。這些憑證將成為 RSocket 連線的中繼資料。登入命令的程式碼如下
private static final MimeType SIMPLE_AUTH = MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()); // (1)
@ShellMethod("Login with your username and password.")
public void login(String username, String password) {
SocketAcceptor responder = RSocketMessageHandler.responder(rsocketStrategies, new ClientHandler());
UsernamePasswordMetadata user = new UsernamePasswordMetadata(username, password); // (2)
this.rsocketRequester = rsocketRequesterBuilder
.setupRoute("shell-client")
.setupData(CLIENT_ID)
.setupMetadata(user, SIMPLE_AUTH) // (3)
.rsocketStrategies(builder ->
builder.encoder(new SimpleAuthenticationEncoder())) // (4)
.rsocketConnector(connector -> connector.acceptor(responder))
.connectTcp("localhost", 7000)
.block();
// ...connection handling code omitted. See the sample for details.
}
此程式碼看起來與舊的建構子程式碼非常相似。就新增安全性而言,最相關的程式碼行如下
SIMPLE_AUTH
靜態變數 (1) 宣告當使用者物件作為連線中繼資料傳遞時應如何編碼。定義了新的 UsernamePasswordMetadata
(2),其中包含使用者在登入時提供的憑證。連線時 (3),setupMetadata()
方法會傳遞 user
物件和在點 (1) 定義的編碼 mimetype。新的 SimpleAuthenticationEncoder
(4) 會放置在用於此連線的 RSocketStrategies
中。此物件負責將 UsernamePasswordMetadata (2) 編碼為正確的 mimetype (1)。
範例程式碼中的進一步變更允許使用者 logout
。這表示使用者可以在身份之間切換,而無需每次都重新啟動用戶端。
當您新增 Spring Security 的依賴項和您的安全性組態類別時,您的程式碼變得更安全。同時,您的整合測試停止運作,因為它不遵守新的安全性設定。
若要修復 RSocketClientToServerITest.java
整合測試,請修改 setupOnce()
方法,以便將使用者物件新增至連線中繼資料。所需的程式碼看起來與您剛在用戶端的登入方法中看到的非常相似
@BeforeAll
public static void setupOnce(@Autowired RSocketRequester.Builder builder,
@LocalRSocketServerPort Integer port,
@Autowired RSocketStrategies strategies) {
SocketAcceptor responder = RSocketMessageHandler.responder(strategies, new ClientHandler());
credentials = new UsernamePasswordMetadata("user", "pass");
mimeType = MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
requester = builder
.setupRoute("shell-client")
.setupData(UUID.randomUUID().toString())
.setupMetadata(credentials, mimeType)
.rsocketStrategies(b ->
b.encoder(new SimpleAuthenticationEncoder()))
.rsocketConnector(connector -> connector.acceptor(responder))
.connectTcp("localhost", port)
.block();
}
現在憑證已新增至連線,測試功能正常運作。若要驗證這一點,請在終端機中導覽至您的 rsocket-server
資料夾並執行 Maven verify
命令。此動作將執行修訂後的整合測試。
./mvnw clean verify
恭喜。您的整合測試現在再次執行並通過!
我在 rsocket-server
範例程式碼中加入了另外兩個整合測試。第一個 RSocketClientToSecuredServerITest.java
使用來自 RSocketSecurityConfig
類別的 test
使用者憑證,以確認伺服器端方法對於沒有 USER
角色的使用者來說是無法存取的。測試方法程式碼如下所示
@Test
public void testFireAndForget() {
// Send a fire-and-forget message
Mono<Void> result = requester
.route("fire-and-forget")
.data(new Message("TEST", "Fire-And-Forget"))
.retrieveMono(Void.class);
// Assert that the user 'test' is DENIED access to the method.
StepVerifier
.create(result)
.verifyErrorMessage("Denied"); // (1)
}
測試斷言 fire and forget 呼叫的結果應該是一個例外,指出使用者被「拒絕」存取 (1)。
另一個新測試斷言,具有虛假憑證的使用者無法取得 RSocket 連線。此測試的程式碼位於檔案 RSocketClientDeniedConnectionToSecuredServerITest.java
中。
最後,請隨時在命令列嘗試更新後的 rsocket-client
。您可以使用各種憑證登入,並嘗試自行存取伺服器端方法。
cd rsocket-client
./mvnw clean package spring-boot:run
# To get help with all the available commands
shell:> help
# To access to all features.
shell:> login user pass
# To access no features.
shell:> login test pass
# To exit the client
shell:> exit
這就是 RSocket 和 Spring Security 的導覽。我希望您覺得它很有用。您也可以觀看 Josh Long 在此 Spring Tips 影片中如何處理相同主題。一如既往,請隨時按讚、分享並在下方留言。如需未來的新聞和更新,何不在 Twitter 上追蹤我?