リアクティブアプリケーション用の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以降に使用されていたデフォルトのフォームよりもはるかに見栄えがよいことがわかります。
これは本番環境に対応したフォームではありませんが、アプリケーションの優れたブートストラップであることに注意してください。
ここでログインしてから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アノテーションをテストメソッドに追加すると、このメソッドの認証済みユーザーを提供できます。
このユーザーのログインとパスワードはそれぞれuserとpasswordであり、役割は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で入手できます。