Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock JWT authenticaiton in a Spring Boot Unit Test?

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 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;
    }
}

Abstract Unit test class:

@ExtendWith(SpringExtension.class)
@SpringBootTest(
    classes = PokerStatApplication.class,
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
public abstract class AbstractUnitTests {

   // mock objects etc


}
like image 831
java12399900 Avatar asked Apr 29 '20 11:04

java12399900


2 Answers

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";

  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);
    }
like image 188
Danil Kuznetsov Avatar answered Oct 05 '22 23:10

Danil Kuznetsov


try with @WithMockUser

    @Test
    @WithMockUser(username="ahmed",roles={"ADMIN"})
    public void shouldGetAllRoundsByUserId() throws Exception {
like image 33
Mukhtiar Ahmed Avatar answered Oct 05 '22 22:10

Mukhtiar Ahmed