Spring Bootを使用してバックエンドを作成していますが、JWTセキュリティを追加したばかりです。
REST Clientを使用していくつかのテストを実行し、JWTセキュリティは正常に機能していますが、ユニットテストはすべて403エラーコードを返しています。
@WithMockUser
アノテーションを追加しましたが、まだ機能していません。
@Test
@WithMockUser
public void shouldRedirectToInstaAuthPage() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/instaAuth")).andExpect(status().is3xxRedirection());
}
ここに欠けている他の構成はありますか?
セキュリティ構成は次のとおりです。
@Configuration
@EnableWebSecurity
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.inMemoryAuthentication()
.withUser("john")
.password("123")
.roles("ADMIN");
}
}
メソッドのセキュリティ:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
私は問題を解決したと信じています(そして、バックエンドで悪い習慣をしたり、セキュリティの脆弱性を作ったりしないことを望みます)。
@ punkrocker27kaのアドバイスに従い、 この回答 を見ました。その中で、彼らはテストのためにOauthトークンを手動で生成しているので、私はJWTトークンに対して同じことをすることにしました。
そこで、JWTトークンを生成し、それらを次のように検証するクラスを更新しました。
public class TokenAuthenticationService {
static final long EXPIRATIONTIME = 864_000_000; // 10 days
static final String SECRET = "ThisIsASecret";
static final String TOKEN_PREFIX = "Bearer";
static final String HEADER_STRING = "Authorization";
public static void addAuthentication(HttpServletResponse res, String username) {
String jwt = createToken(username);
res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + jwt);
}
public static Authentication getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
return user != null ?
new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList()) :
null;
}
return null;
}
public static String createToken(String username) {
String jwt = Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
return jwt;
}
}
そして、そのための新しいテストを作成しました。
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class TokenAuthenticationServiceTest {
@Autowired
private MockMvc mvc;
@Test
public void shouldNotAllowAccessToUnauthenticatedUsers() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/test")).andExpect(status().isForbidden());
}
@Test
public void shouldGenerateAuthToken() throws Exception {
String token = TokenAuthenticationService.createToken("john");
assertNotNull(token);
mvc.perform(MockMvcRequestBuilders.get("/test").header("Authorization", token)).andExpect(status().isOk());
}
}
その後、テストを実行して合格したので、@WithMockUser
アノテーションを必要とせずにトークンが受け入れられました。これを他のテストクラスに追加します。
PS:テストエンドポイントは以下です。
/**
* This controller is used only for testing purposes.
* Especially to check if the JWT authentication is ok.
*/
@RestController
public class TestController {
@RequestMapping(path = "/test", method = RequestMethod.GET)
public String testEndpoint() {
return "Hello World!";
}
}
このcreateToken()メソッドを使用してテストするときに注意する必要があることの1つは、存在しないユーザーに対してテストをテストできないことです。これは、createToken()が、入力した文字列に基づいてJWTトークンのみを作成するためです。存在しないユーザーがアクセスできないようにするには、次のようにcreateToken()メソッドをプライベートにし、代わりにリクエストを使用してトークンを取得することをお勧めします。
@Test
public void existentUserCanGetTokenAndAuthentication() throws Exception {
String username = "existentuser";
String password = "password";
String body = "{\"username\":\"" + username + "\", \"password\":\"
+ password + "\"}";
MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/v2/token")
.content(body))
.andExpect(status().isOk()).andReturn();
String response = result.getResponse().getContentAsString();
response = response.replace("{\"access_token\": \"", "");
String token = response.replace("\"}", "");
mvc.perform(MockMvcRequestBuilders.get("/test")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk());
}
同様の方法で、存在しないユーザーがこの結果を取得できないことを示すことができます。
@Test
public void nonexistentUserCannotGetToken() throws Exception {
String username = "existentuser";
String password = "password";
String body = "{\"username\":\"" + username + "\", \"password\":\"
+ password + "\"}";
mvc.perform(MockMvcRequestBuilders.post("/v2/token")
.content(body))
.andExpect(status().isForbidden()).andReturn();
}