I have added JWT Authentication using Auth0 to my Spring Boot REST API following this example.
Now, as expected, my previously working Controller
unit tests give a response code of401 Unauthorized
rather than 200 OK
as I am not passing any JWT in the tests.
How can I mock the JWT/Authentication
part of my REST Controller tests?
Unit test class
@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 }
Requests class (used above)
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 Security 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; } }
Abstract Unit test class
@ExtendWith(SpringExtension.class) @SpringBootTest( classes = PokerStatApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT ) public abstract class AbstractUnitTests { // mock objects etc }
Advertisement
Answer
If I understand correctly your case there is one of the solutions.
In most cases, JwtDecoder
bean performs token parsing and validation if the token exists in the request headers.
Example from your configuration:
@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; }
So for the tests, you need to add stub of this bean and also for replacing this bean in spring context, you need the test configuration with it.
It can be some things like this:
@TestConfiguration public class TestSecurityConfig { static final String AUTH0_TOKEN = "token"; static final String SUB = "sub"; static final String AUTH0ID = "sms|12345678"; @Bean 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 ); } }
For using this configuration in tests just pick up this test security config:
@SpringBootTest(classes = TestSecurityConfig.class)
Also in the test request should be authorization header with a token like Bearer .. something
.
Here is an example regarding your configuration:
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); }