前回は認可をテストしてみたので、認可についてもテストしてみようかと思います。
認可については認証から切り離してテストすることができましたが、認証は認可と切り離さずテストする感じですね。というのも、このやり方は AuthenticationFilter ごと実行させることになるので認証認可に関係する部分はすべて動作します。
ただ、認証認可に関する処理を一つのテストで網羅してしまうと、一つのテストに複数のテスト対象が入り込んでしまい複雑になってしまいます。なので、認証のテストではログインページへのアクセスなど、認可処理が入り込まないエンドポイントなどを使用して、テストをシンプルに保つのがいいのではないかと思います。
では、実装していきましょう。
コードは前回の記事のものをベースに拡張していきます。
Configuration クラスにパスワードエンコーダーをBean登録します。これがないと AuthenticationProvider がパスワードの突合ができず、テストが失敗してしまいます。
package com.example.spring.security.test.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public UserDetailsService userDetailsService() { var userDetailsManager = new InMemoryUserDetailsManager(); userDetailsManager.createUser( User.withUsername("alice") .password("12345") .roles("admin") .build() ); userDetailsManager.createUser( User.withUsername("bob") .password("12345") .roles("staff") .build() ); return userDetailsManager; } @SuppressWarnings("deprecation") @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin(); http.authorizeRequests() .mvcMatchers("/admin/**").hasRole("admin") .mvcMatchers("/users/**").hasAnyRole("admin", "staff") .anyRequest().authenticated(); } }
で、テストコードがこちら。
package com.example.spring.security.test; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; @SpringBootTest @AutoConfigureMockMvc public class AuthenticationTest { @Autowired MockMvc mockMvc; @Test public void formLoginWithInvalidCredential() throws Exception { mockMvc.perform(formLogin().user("bob").password("11111")) .andExpect(unauthenticated()); } @Test public void formLoginWithValidCredential() throws Exception { mockMvc.perform(formLogin().user("bob").password("12345")) .andExpect(authenticated().withRoles("staff")); } }
formLogin()
という RequestBuilder
の実装を使うことでフォームログインを行います。この実装はSpringSecurityTestの機能の一つで、フォームログインを簡単にテストできるようにしてくれています。メソッドチェーンでユーザー名とパスワードを指定する感じですね。
authenticated()
unauthenticated()
でログインが成功/失敗したことを検証できます。続けて withRoles
などでログイン後の認証情報も検証できるようです。
ソースはこちらにアップしてあります。