用於響應式應用程序的Spring Security 5

1.簡介

在本文中,我們將探索Spring Security 5框架的新功能,以保護反應性應用程序。該版本與Spring 5和Spring Boot 2保持一致。

在本文中,我們不會詳細介紹反應式應用程序本身,這是Spring 5框架的新功能。請務必查看文章Spring Reactor簡介以獲得更多詳細信息。

2. Maven安裝

我們將使用Spring Boot啟動器來引導我們的項目以及所有必需的依賴項。

基本設置需要父聲明,Web Starter和安全性Starter依賴項。我們還需要Spring Security測試框架:

<parent>

 <groupId>org.springframework.boot</groupId>

 <artifactId>spring-boot-starter-parent</artifactId>

 <version>2.2.6.RELEASE</version>

 <relativePath/>

 </parent>



 <dependencies>

 <dependency>

 <groupId>org.springframework.boot</groupId>

 <artifactId>spring-boot-starter-webflux</artifactId>

 </dependency>

 <dependency>

 <groupId>org.springframework.boot</groupId>

 <artifactId>spring-boot-starter-security</artifactId>

 </dependency>

 <dependency>

 <groupId>org.springframework.security</groupId>

 <artifactId>spring-security-test</artifactId>

 <scope>test</scope>

 </dependency>

 </dependencies>

我們可以在Maven Central上查看Spring Boot安全啟動程序的當前版本。

3.項目設置

3.1 啓動相應式應用程序

我們將不使用標準的@SpringBootApplication配置,而是配置基於Netty的Web服務器。 Netty是一個基於NIO的異步框架,為響應式應用程序奠定了良好的基礎。

@EnableWebFlux批註為應用程序啟用標準的Spring Web Reactive配置:

@ComponentScan(basePackages = {"com.baeldung.security"})

 @EnableWebFlux

 public class SpringSecurity5Application {



 public static void main(String[] args) {

 try (AnnotationConfigApplicationContext context

 = new AnnotationConfigApplicationContext(

 SpringSecurity5Application.class)) {



 context.getBean(NettyContext.class).onClose().block();

 }

 }

在這裡,我們創建一個新的應用程序上下文,並通過在Netty上下文上調用.onClose().block()鍊等待Netty關閉。

Netty關閉後,將使用try-with-resources塊自動關閉上下文。

我們還需要創建一個基於Netty的HTTP服務器,一個用於HTTP請求的處理程序以及該服務器和處理程序之間的適配器:

@Bean

 public NettyContext nettyContext(ApplicationContext context) {

 HttpHandler handler = WebHttpHandlerBuilder

 .applicationContext(context).build();

 ReactorHttpHandlerAdapter adapter

 = new ReactorHttpHandlerAdapter(handler);

 HttpServer httpServer = HttpServer.create("localhost", 8080);

 return httpServer.newHandler(adapter).block();

 }

3.2。 Spring安全配置類

對於我們的基本Spring Security配置,我們將創建一個配置類– SecurityConfig

要在Spring Security 5中啟用WebFlux支持,我們只需要指定@EnableWebFluxSecurity批註:

@EnableWebFluxSecurity

 public class SecurityConfig {

 // ...

 }

現在,我們可以利用ServerHttpSecurity類來構建我們的安全配置。

該類是Spring 5的新功能。它類似於HttpSecurity構建器,但僅對WebFlux應用程序啟用。

ServerHttpSecurity已經使用一些合理的默認值進行了預配置,因此我們可以完全跳過此配置。但是對於初學者來說,我們將提供以下最小配置:

@Bean

 public SecurityWebFilterChain securitygWebFilterChain(

 ServerHttpSecurity http) {

 return http.authorizeExchange()

 .anyExchange().authenticated()

 .and().build();

 }

另外,我們需要用戶詳細信息服務。 Spring Security為我們提供了一個方便的模擬用戶構建器以及用戶詳細信息服務的內存實現:

@Bean

 public MapReactiveUserDetailsService userDetailsService() {

 UserDetails user = User

 .withUsername("user")

 .password(passwordEncoder().encode("password"))

 .roles("USER")

 .build();

 return new MapReactiveUserDetailsService(user);

 }

由於我們處在被動狀態,因此用戶詳細信息服務也應處於被動狀態。如果我們查看ReactiveUserDetailsService接口,我們將看到它的findByUsername方法實際上返回了Mono發布者:

public interface ReactiveUserDetailsService {



 Mono<UserDetails> findByUsername(String username);

 }

現在,我們可以運行我們的應用程序,並觀察常規的HTTP基本身份驗證表單。

4. 登錄表單樣式

Spring Security 5中的一個小但引人注目的改進是使用Bootstrap 4 CSS框架的新型登錄表單。登錄表單中的樣式錶鍊接到CDN,因此我們只有在連接到Internet時才能看到改進。

要使用新的登錄表單,我們將相應的formLogin()構建器方法添加到ServerHttpSecurity構建器:

public SecurityWebFilterChain securitygWebFilterChain(

 ServerHttpSecurity http) {

 return http.authorizeExchange()

 .anyExchange().authenticated()

 .and().formLogin()

 .and().build();

 }

如果現在打開應用程序的主頁,將會發現它看起來比自從早期版本的Spring Security以來我們習慣的默認表單要好得多:

用於響應式應用程序的Spring

請注意,這不是可用於生產的表單,但這是我們應用程序的良好引導。

如果現在登錄,然後轉到http:// localhost:8080 / logout URL,我們將看到註銷確認表單,該表單也具有樣式。

5.反應性控制器安全

要查看身份驗證表單的內容,讓我們實現一個簡單的反應式控制器來向用戶打招呼:

@RestController

 public class GreetController {



 @GetMapping("/")

 public Mono<String> greet(Mono<Principal> principal) {

 return principal

 .map(Principal::getName)

 .map(name -> String.format("Hello, %s", name));

 }



 }

登錄後,我們將看到問候。讓我們添加另一個只能由管理員訪問的反應式處理程序:

@GetMapping("/admin")

 public Mono<String> greetAdmin(Mono<Principal> principal) {

 return principal

 .map(Principal::getName)

 .map(name -> String.format("Admin access: %s", name));

 }

現在,讓我們在用戶詳細信息服務中創建一個角色為ADMIN的第二個用戶:

UserDetails admin = User.withDefaultPasswordEncoder()

 .username("admin")

 .password("password")

 .roles("ADMIN")

 .build();

現在,我們可以為管理員URL添加一個匹配器規則,該規則要求用戶具有ROLE_ADMIN權限。

請注意,我們必須在.anyExchange()**鏈調用之前放置匹配器**。此調用適用於其他匹配器尚未覆蓋的所有其他URL:

return http.authorizeExchange()

 .pathMatchers("/admin").hasAuthority("ROLE_ADMIN")

 .anyExchange().authenticated()

 .and().formLogin()

 .and().build();

如果現在使用useradmin登錄,我們將看到它們都遵循初始問候語,因為我們已使所有經過身份驗證的用戶都可以訪問它。

但是只有admin用戶可以訪問http://localhost:8080/admin URL並看到她的問候

6.反應性方法的安全性

我們已經看到瞭如何保護URL的安全,但是方法呢?

要為反應性方法啟用基於方法的安全性,我們只需要在SecurityConfig類中添加@EnableReactiveMethodSecurity批註:

@EnableWebFluxSecurity

 @EnableReactiveMethodSecurity

 public class SecurityConfig {

 // ...

 }

現在,讓我們創建一個具有以下內容的響應式問候服務:

@Service

 public class GreetService {



 public Mono<String> greet() {

 return Mono.just("Hello from service!");

 }

 }

我們可以將其註入到控制器中,轉到http://localhost:8080/greetService ,看看它是否確實有效:

@RestController

 public class GreetController {



 private GreetService greetService



 @GetMapping("/greetService")

 public Mono<String> greetService() {

 return greetService.greet();

 }



 // standard constructors...

 }

但是,如果我們現在在具有ADMIN角色的服務方法上添加@PreAuthorize批註,那麼普通用戶將無法訪問問候服務URL:

@Service

 public class GreetService {



 @PreAuthorize("hasRole('ADMIN')")

 public Mono<String> greet() {

 // ...

 }

7.在測試中模擬用戶

讓我們看看測試我們的響應式Spring應用程序有多麼容易。

首先,我們將使用注入的應用程序上下文創建一個測試:

@ContextConfiguration(classes = SpringSecurity5Application.class)

 public class SecurityTest {



 @Autowired

 ApplicationContext context;



 // ...

 }

現在,我們將建立一個簡單的反應式Web測試客戶端,這是Spring 5測試框架的功能:

@Before

 public void setup() {

 this.rest = WebTestClient

 .bindToApplicationContext(this.context)

 .configureClient()

 .build();

 }

這使我們可以快速檢查未經授權的用戶是否從應用程序的主頁重定向到登錄頁面:

@Test

 public void whenNoCredentials_thenRedirectToLogin() {

 this.rest.get()

 .uri("/")

 .exchange()

 .expectStatus().is3xxRedirection();

 }

如果現在將@MockWithUser批註添加到測試方法,則可以為該方法提供經過身份驗證的用戶。

該用戶的登錄名和密碼分別為userpassword ,角色為USER 。當然,可以全部使用@MockWithUser註釋參數進行配置。

現在,我們可以檢查授權用戶是否看到了問候語:

@Test

 @WithMockUser

 public void whenHasCredentials_thenSeesGreeting() {

 this.rest.get()

 .uri("/")

 .exchange()

 .expectStatus().isOk()

 .expectBody(String.class).isEqualTo("Hello, user");

 }

從Spring Security 4開始, @WithMockUser批註可用。但是,在Spring Security 5中,它也進行了更新,以涵蓋反應式端點和方法。

8.結論

在本教程中,我們發現了即將發布的Spring Security 5版本的新功能,特別是在反應式編程領域。

與往常一樣,可以在GitHub上獲得本文的源代碼。