Spring BootにJWT
Authentication using Auth0
を追加しましたREST APIの後に この例 を追加しました。
予想どおり、以前に機能していたController
単体テストでは、テストでJWTを渡していないため、401 Unauthorized
ではなく200 OK
の応答コードが返されます。
RESTコントローラテストのJWT/Authentication
部分をモックするにはどうすればよいですか?
単体テストクラス:
@AutoConfigureMockMvc
public class UserRoundsControllerTest extends AbstractUnitTests {
private static String STUB_USER_ID = "user3";
private static String STUB_ROUND_ID = "7e3b270222252b2dadd547fb";
@Autowired
private MockMvc mockMvc;
private Round round;
private ObjectId objectId;
@BeforeEach
public void setUp() {
initMocks(this);
round = Mocks.roundOne();
objectId = Mocks.objectId();
}
@Test
public void shouldGetAllRoundsByUserId() throws Exception {
// setup
given(userRoundService.getAllRoundsByUserId(STUB_USER_ID)).willReturn(Collections.singletonList(round));
// mock the rounds/userId request
RequestBuilder requestBuilder = Requests.getAllRoundsByUserId(STUB_USER_ID);
// perform the requests
MockHttpServletResponse response = mockMvc.perform(requestBuilder)
.andReturn()
.getResponse();
// asserts
assertNotNull(response);
assertEquals(HttpStatus.OK.value(), response.getStatus());
}
//other tests
}
リクエストクラス(上記で使用):
public class Requests {
private Requests() {
}
public static RequestBuilder getAllRoundsByUserId(String userId) {
return MockMvcRequestBuilders
.get("/users/" + userId + "/rounds/")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON);
}
}
Spring Securty Config:
/**
* Configures our application with Spring Security to restrict access to our API endpoints.
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${auth0.audience}")
private String audience;
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
@Override
public void configure(HttpSecurity http) throws Exception {
/*
This is where we configure the security required for our endpoints and setup our app to serve as
an OAuth2 Resource Server, using JWT validation.
*/
http.cors().and().csrf().disable().sessionManagement().
sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.mvcMatchers(HttpMethod.GET,"/users/**").authenticated()
.mvcMatchers(HttpMethod.POST,"/users/**").authenticated()
.mvcMatchers(HttpMethod.DELETE,"/users/**").authenticated()
.mvcMatchers(HttpMethod.PUT,"/users/**").authenticated()
.and()
.oauth2ResourceServer().jwt();
}
@Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
抽象ユニットテストクラス:
@ExtendWith(SpringExtension.class)
@SpringBootTest(
classes = PokerStatApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
public abstract class AbstractUnitTests {
// mock objects etc
}
私があなたのケースを正しく理解していれば、解決策の1つがあります。
ほとんどの場合、JwtDecoder
Beanは、トークンが要求ヘッダーに存在する場合、トークンの解析と検証を実行します。
あなたの設定の例:
_ @Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
_
したがって、テストでは、このBeanのスタブを追加する必要があります。また、SpringコンテキストでこのBeanを置き換えるには、テスト構成を使用する必要があります。
次のような場合があります。
_@TestConfiguration
public class TestSecurityConfig {
static final String AUTH0_TOKEN = "token";
static final String SUB = "sub";
static final String AUTH0ID = "sms|12345678";
public JwtDecoder jwtDecoder() {
// This anonymous class needs for the possibility of using SpyBean in test methods
// Lambda cannot be a spy with spring @SpyBean annotation
return new JwtDecoder() {
@Override
public Jwt decode(String token) {
return jwt();
}
};
}
public Jwt jwt() {
// This is a place to add general and maybe custom claims which should be available after parsing token in the live system
Map<String, Object> claims = Map.of(
SUB, USER_AUTH0ID
);
//This is an object that represents contents of jwt token after parsing
return new Jwt(
AUTH0_TOKEN,
Instant.now(),
Instant.now().plusSeconds(30),
Map.of("alg", "none"),
claims
);
}
}
_
テストでこの構成を使用するには、このテストセキュリティ構成を取得するだけです。
@SpringBootTest(classes = TestSecurityConfig.class)
また、テストリクエストには、_Bearer .. something
_のようなトークンを含む認証ヘッダーを含める必要があります。
設定に関する例は次のとおりです。
_ public static RequestBuilder getAllRoundsByUserId(String userId) {
return MockMvcRequestBuilders
.get("/users/" + userId + "/rounds/")
.accept(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, "Bearer token"))
.contentType(MediaType.APPLICATION_JSON);
}
_
アプリケーションの一部をテストする場合は、必要な構成のみをロードする必要があります。あなたのケースでは、セキュリティ設定を含むコンテキスト全体をロードする@WebMvcTest(controllers = ...,...,)
の代わりに@SpringBootTest(...)
を使用してコントローラのみをロードする必要があります。交換するだけで期待どおりに機能するはずです
統合テストを作成してアプリが適切に保護されていることを確認する必要があることに注意してください。今回は@SpringBootTest(...)
を使用してコンテキスト全体をロードし、auth0にテストユーザーを作成して、それを使用して実装をテストする
create application.properties in test/resources(メインをオーバーライドしますが、テストステージのみ)
指定によるセキュリティの無効化:
security.ignored=/**
security.basic.enable= false
spring.autoconfigure.exclude= org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
@WithMockUser
@Test
@WithMockUser(username="ahmed",roles={"ADMIN"})
public void shouldGetAllRoundsByUserId() throws Exception {
Bearerトークンを取得して、HTTPヘッダーとして渡すことができます。以下は、参照用のテストメソッドのサンプルスニペットです。
@Test
public void existentUserCanGetTokenAndAuthentication() throws Exception {
String username = "existentuser";
String password = "password";
String body = "{\"username\":\"" + username + "\", \"password\":\"
+ password + "\"}";
MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/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("/users/" + userId + "/rounds")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk());
}