Spring Security ユニットテスト
概要
Spring Securityを使用する場合、ユニットテスト(単体テスト)をする方法について調べて見ました。
※本記事で紹介する内容はSpring Security 4.1から追加されました。
既存のテスト方法
Spring SecurityはSecurityContext
を通して、現在実行しているスレッドと関連している認証情報を管理します。したがって、テスト実行時にSecurityContextHolder
を介して認証情報を設定すると、ログインした状態などの認証に関するテストができます。
class SomeTest { //... @Before public void setUp() { SecurityContextHolder.getContext() .setAuthentication(new UsernamePasswordAuthenticationToken("username", "password", Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")))); } //... }
※ 上記のようにユニットテストを実行する前に認証情報を設定すると、ログインした状態でテストが可能です。しかし、上記のようなコードは、やはりSpringらしくないですね。
新しく追加された機能
Spring Security 4.1からアノテーション(Annotation)を通した宣言的なテスティングフィチャーが追加されました。
アノテーション | 説明 |
---|---|
@WithAnonymousUser | 匿名ユーザの認証情報を設定するためのアノテーション |
@WithUserDetails | UserDetailsService からユーザー情報を取得して認証情報を設定するためのアノテーション(4.0から追加され、4.1からはSpring Bean名も指定できるようになりました) |
@WithMockUser | 別のUserDetailsService のスタブを作成しなくても簡単に認証情報の設定ができるアノテーション(4.0から追加、4.1から動作する) |
使い方
設定
dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' runtimeOnly 'org.springframework.boot:spring-boot-devtools' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' }
まず、Gradle
などでSpring Securityに関する依存性を設定する必要があります。テストスコープでorg.springframework.security:spring-security-test
モジュールを追加します。
@WithAnonymousUser
認証されていない状態を設定するためのアノテーションです。認証していない場合を示すアノテーションがなぜ必要かと思われますが、前述のようにSpring Securityは、現在実行中のスレッドに関連づけて、認証情報を管理するため、一度の認証情報を設定してテストを行った場合に、認証状態を初期化させる必要があります。そうしないと次のテストで以前のテストの認証情報がそのまま使われてしまいます。
class SomeTest { //... @Test @WithAnonymousUser public void some_test() { ... } //... }
@WithUserDetails
Spring Securityでユーザーの情報を照会するために使われるUserDetailsService
を使って認証情報を設定するアノテーションです。
class SomeTestConfiguration { //... @Bean @Profile("test") public UserDetailsService userDetailsService() { return new UserDetailsService() { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return User .withUsername(username) .password("password") .authorities(new SimpleGrantedAuthority("ROLE_USER")) .build(); } }; } //... }
class SomeTest { //... @Test @WithUserDetails("test_user") public void some_test() { ... } //... }
上記のようにテスト用のUserDetailsService
のBeanを登録して使います。
@WithMockUser
単にusernameとroleくらいの記述するこてで、認証情報を設定してくれるアノテーションです。別の設定がいらないので簡単に使えます。
class SomeTest { //... @Test @WithMockUser(username = "username", roles = "USER") public void some_test() { ... } //... }
カスタムアノテーション
ほとんどの場合、上のコードで紹介したアノテーション(Annotation)を使用すると、テストが可能になると思われるが、認証主体(AuthenticationPrincipal)に関する情報をUserDetailsを使用していない場合には、別のカスタムアノテーションを作成することができます。
@Retention(RetentionPolicy.RUNTIME) @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) public @interface WithCustomMockUser { String userNo() default "1"; String userId() default "user"; String name() default "name"; }
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithCustomMockUser> { @Override public SecurityContext createSecurityContext(WithCustomMockUser user) { SecurityContext context = SecurityContextHolder.createEmptyContext(); CustomUser principal = new CustomUser(Long.valueOf(user.memberNo()), user.userId(), user.name()); Authentication auth = new UsernamePasswordAuthenticationToken(principal, "password", Arrays.asList(new SimpleGrantedAuthority("ROLE_MEMBER"))); context.setAuthentication(auth); return context; } }
上記のようにカスタムアノテーションを作成し、他のテストアノテーションと同様に使用すると、カスタム認証情報が設定された状態でテストができます。
class SomeTest { //... @Test @WithCustomMockUser public void some_test() { //... } //... }
合成アノテーション(Composition Annotation)
また、合成アノテーションを作成し、繰り返されるコードを省略することができます。
@Retention(RetentionPolicy.RUNTIME) @WithMockUser(value="admin",roles="ADMIN") public @interface WithCustomMockAdmin { }
class SomeTest { //... @Test @WithCustomMockAdmin public void some_test() { ... } //... }
まとめ
今まではログインなどの認証状態をテストする場合、面倒なコードを書かなければなりませんでしたが、Spring Securityで提供するテスティングフィチャーを使用することで、簡単にテストすることができますので、より安全なアプリケーションを作成することができます。