リアクティブアプリケーション用のSpring Security 5

リアクティブアプリケーション用のSpring Security 5

1. 前書き

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

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

2. Mavenセットアップ

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

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


    org.springframework.boot
    spring-boot-starter-parent
    2.0.0.RELEASE
    



    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.springframework.boot
        spring-boot-starter-security
    
    
        org.springframework.security
        spring-security-test
        test
    

Spring Bootセキュリティスターターover at Maven Centralの現在のバージョンを確認できます。

3. プロジェクトのセットアップ

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

標準の@SpringBootApplication構成は使用しませんが、代わりにNettyベースのWebサーバーを構成します。 Netty is an asynchronous NIO-based framework which is a good foundation for reactive applications.

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

@ComponentScan(basePackages = {"com.example.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を利用してセキュリティ構成を構築できます。

This class is a new feature of 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インターフェースをチェックアウトすると、we’ll see that its findByUsername method actually returns a Mono publisher:

public interface ReactiveUserDetailsService {

    Mono findByUsername(String username);
}

これで、アプリケーションを実行して、通常のHTTP基本認証フォームを確認できます。

4. スタイル付きログインフォーム

Spring Security 5の小さいながらも顕著な改善点は、Bootstrap 4 CSSフレームワークを使用する新しいスタイルのログインフォームです。 ログインフォームのスタイルシートはCDNにリンクしているため、インターネットに接続した場合にのみ改善が見られます。

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

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

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

image

 

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

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

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

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

@RestController
public class GreetController {

    @GetMapping("/")
    public Mono greet(Mono principal) {
        return principal
          .map(Principal::getName)
          .map(name -> String.format("Hello, %s", name));
    }

}

ログインすると、挨拶が表示されます。 管理者のみがアクセスできる別のリアクティブハンドラーを追加しましょう。

@GetMapping("/admin")
public Mono greetAdmin(Mono 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のマッチャールールを追加できます。

Note that we have to put matchers before the .anyExchange()のチェーン呼び出し。 この呼び出しは、他のマッチャーでまだカバーされていない他のすべてのURLに適用されます。

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

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

But only the admin user can go to the http://localhost:8080/admin URL and see her greeting

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

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

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

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

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

@Service
public class GreetService {

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

それをコントローラーに注入し、http://localhost:8080/greetServiceに移動して、実際に機能することを確認できます。

@RestController
public class GreetController {

    private GreetService greetService

    @GetMapping("/greetService")
    public Mono greetService() {
        return greetService.greet();
    }

    // standard constructors...
}

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

@Service
public class GreetService {

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

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

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

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

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

    @Autowired
    ApplicationContext context;

    // ...
}

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

@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");
}

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

8. 結論

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

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