1. 序章

この記事では、リアクティブアプリケーションを保護するための Spring Security5フレームワークの新機能について説明します。 このリリースは、Spring5およびSpringBoot2に対応しています。

この記事では、Spring5フレームワークの新機能であるリアクティブアプリケーション自体については詳しく説明しません。 詳細については、記事 Intro to ReactorCoreを確認してください。

2. Mavenのセットアップ

Spring Bootスターターを使用して、必要なすべての依存関係とともにプロジェクトをブートストラップします。

基本的なセットアップには、親宣言、Webスターター、およびセキュリティスターターの依存関係が必要です。 SpringSecurityテストフレームワークも必要です。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.1</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>

Spring Bootセキュリティスターターの現在のバージョンは、MavenCentralで確認できます。

3. プロジェクトの設定

3.1. リアクティブアプリケーションのブートストラップ

標準の@SpringBootApplication構成は使用しませんが、代わりにNettyベースのWebサーバーを構成します。 Nettyは、非同期NIOベースのフレームワークであり、リアクティブアプリケーションの優れた基盤です。

@EnableWebFlux アノテーションは、アプリケーションの標準のSpringWebReactive構成を有効にします。

@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セキュリティ構成クラス

基本的なSpringSecurity構成では、構成クラスSecurityConfigを作成します。

Spring Security 5でWebFluxサポートを有効にするには、@EnableWebFluxSecurityアノテーションを指定するだけです。

@EnableWebFluxSecurity
public class SecurityConfig {
    // ...
}

これで、クラスServerHttpSecurityを利用してセキュリティ構成を構築できます。

このクラスはSpring5の新機能です。 に似ています HttpSecurity ビルダーですが、WebFluxアプリケーションでのみ有効になっています。

ServerHttpSecurity は、いくつかの適切なデフォルトですでに事前構成されているため、この構成を完全にスキップできます。 ただし、初心者向けに、次の最小限の構成を提供します。

@Bean
public SecurityWebFilterChain securityWebFilterChain(
  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の小さいながらも目覚ましい改善は、Bootstrap4CSSフレームワークを使用する新しいスタイルのログインフォームです。 ログインフォームのスタイルシートはCDNにリンクしているため、インターネットに接続した場合にのみ改善が見られます。

新しいログインフォームを使用するには、対応する formLogin()ビルダーメソッドをServerHttpSecurityビルダーに追加しましょう。

public SecurityWebFilterChain securityWebFilterChain(
  ServerHttpSecurity http) {
    return http.authorizeExchange()
      .anyExchange().authenticated()
      .and().formLogin()
      .and().build();
}

アプリケーションのメインページを開くと、以前のバージョンのSpringSecurity以降に使用されていたデフォルトのフォームよりもはるかに見栄えがよいことがわかります。

 

これは本番環境に対応したフォームではありませんが、アプリケーションの優れたブートストラップであることに注意してください。

ここでログインしてからhttp:// localhost:8080 / logout URLにアクセスすると、ログアウト確認フォームが表示されます。これもスタイルが設定されています。

5. リアクティブコントローラーのセキュリティ

認証フォームの背後にあるものを確認するために、ユーザーに挨拶する単純なリアクティブコントローラーを実装しましょう。

@RestController
public class GreetingController {

    @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という役割を持つ2番目のユーザーを作成しましょう。

UserDetails admin = User.withDefaultPasswordEncoder()
  .username("admin")
  .password("password")
  .roles("ADMIN")
  .build();

これで、ユーザーにROLE_ADMIN権限が必要な管理URLのマッチャールールを追加できます。

.anyExchange()チェーン呼び出しの前にマッチャーを配置する必要があることに注意してください。 この呼び出しは、他のマッチャーによってまだカバーされていない他のすべてのURLに適用されます。

return http.authorizeExchange()
  .pathMatchers("/admin").hasAuthority("ROLE_ADMIN")
  .anyExchange().authenticated()
  .and().formLogin()
  .and().build();

userまたはadminでログインすると、認証されたすべてのユーザーがアクセスできるようになっているため、両方が最初の挨拶を監視していることがわかります。

ただし、管理者ユーザーのみがhttp:// localhost:8080 / admin URLにアクセスして、挨拶を見ることができます

6. リアクティブメソッドのセキュリティ

URLを保護する方法を見てきましたが、メソッドについてはどうでしょうか。

リアクティブメソッドのメソッドベースのセキュリティを有効にするには、@EnableReactiveMethodSecurityアノテーションをSecurityConfigクラスに追加するだけです。

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
    // ...
}

次に、次のコンテンツを使用してリアクティブグリーティングサービスを作成しましょう。

@Service
public class GreetingService {

    public Mono<String> greet() {
        return Mono.just("Hello from service!");
    }
}

これをコントローラーに挿入し、 http:// localhost:8080 / greetingService にアクセスして、実際に機能することを確認できます。

@RestController
public class GreetingController {

    private GreetingService greetingService

    // constructor...

    @GetMapping("/greetingService")
    public Mono<String> greetingService() {
        return greetingService.greet();
    }

}

ただし、ADMINロールを使用してサービスメソッドに@PreAuthorizeアノテーションを追加すると、通常のユーザーはgreetサービスのURLにアクセスできなくなります。

@Service
public class GreetingService {

    @PreAuthorize("hasRole('ADMIN')")
    public Mono<String> greet() {
        // ...
    }
}

7. テストでユーザーをあざける

リアクティブSpringアプリケーションのテストがいかに簡単かを確認しましょう。

まず、注入されたアプリケーションコンテキストを使用してテストを作成します。

@ContextConfiguration(classes = SpringSecurity5Application.class)
public class SecurityTest {

    @Autowired
    ApplicationContext context;

    // ...
}

次に、Spring5テストフレームワークの機能である単純なリアクティブWebテストクライアントをセットアップします。

@Before
public void setup() {
    this.webTestClient = WebTestClient
      .bindToApplicationContext(this.context)
      .configureClient()
      .build();
}

これにより、許可されていないユーザーがアプリケーションのメインページからログインページにリダイレクトされていることをすばやく確認できます。

@Test
public void whenNoCredentials_thenRedirectToLogin() {
    webTestClient.get()
      .uri("/")
      .exchange()
      .expectStatus().is3xxRedirection();
}

ここで、 @WithMockUser アノテーションをテストメソッドに追加すると、このメソッドの認証済みユーザーを提供できます。

このユーザーのログインとパスワードはそれぞれuserpasswordであり、役割はUSERです。 もちろん、これはすべて@WithMockUserアノテーションパラメーターを使用して構成できます。

これで、許可されたユーザーに挨拶が表示されることを確認できます。

@Test
@WithMockUser
public void whenHasCredentials_thenSeesGreeting() {
    webTestClient.get()
      .uri("/")
      .exchange()
      .expectStatus().isOk()
      .expectBody(String.class).isEqualTo("Hello, user");
}

@WithMockUser アノテーションは、SpringSecurity4以降で使用できます。 ただし、これはSpring Security 5でも更新され、リアクティブエンドポイントとメソッドをカバーしています。

8. 結論

このチュートリアルでは、特にリアクティブプログラミングの分野で、次のSpringSecurity5リリースの新機能を発見しました。

いつものように、記事のソースコードはGitHubから入手できます。