Search code examples
javaspringtestingjwt

Cannot mock JWT Spring resource server for testing


My authorization server is a self-hosted keycloak instance, which I'd rather mock for testing, than access real one. Now, I've defined my security filter like this:

@Bean
    public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception{
        http.csrf(AbstractHttpConfigurer::disable);
        http.authorizeHttpRequests(requests -> requests
                                    .anyRequest().hasRole("USER")
                                    );
        http.oauth2ResourceServer(oauth2 -> {
            oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter));
            oauth2.authenticationEntryPoint(unauthorizedHandler);
            }
            );
//        http.addFilterAfter()
        http.exceptionHandling(exception -> {
            exception.accessDeniedHandler(accessDeniedHandler);
        });
        return http.build();
    }

Now, I've made been to mock JWT authorization like this:

@Bean
    public SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor jwtRequestPostProcessor() {
        SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor data = jwt().jwt(builder -> {
            builder.issuer("http://auth.localhost/realms/CompetitionManager");
            builder.claim("azp", "competition-manager-web");
            builder.claim("preferred_username", "testUser");
            builder.claim("email", "test@example.org");
            builder.claim("resource_access", Map.of("competition-manager-web", Map.of("roles", List.of("USER"))));
        });
        return data;
    }

Supposedly, there are keys missing, but according to docs, this will be handled by the spring security And the test looks like this:

@SpringJUnitConfig(classes = {JwtTestConfig.class})
public class ContestControllerTest {
    
    /* Autowiring JPA repositories & interfaces */
    
    
    @Autowired
    private SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor defaultJwt;

    @Test
    @Order(2)
    public void getListOfContests_then200() throws Exception {
        mockMvc.perform(get("/contests/list").with(defaultJwt))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$").isArray())
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[*]", containsInAnyOrder("visible test contest 1", "visible test contest 2")));
    }

Just as specified in docs, however it does not go through. Token is passed correctly, that's for sure, since I get 403 not 401, and, in the other test cases, the preferred_username is accessed & used, but it does not pass through authorization filters, and I only get short message "Access Denied" without any details. Am I missing something in my token definition?


Solution

  • When using SecurityMockMvcRequestPostProcessors, your jwtAuthConverter bean is not used. What the post-processor does is building the JwtAuthenticationToken by itself.

    Two options:

    • set explicitly the authorities you want when configuring the post-processors (please note that I wrote "the authorities" and not "the claims you expect to be mapped to authorities")
    • use @WhithJwt or @WithMockJwtAuth from spring-addons-oauth2-test. Both will use your jwtAuthConverter if it is exposed as a @Bean. Usage on the project Github repo or in this Baeldung article

    Disclaimer

    I wrote all that (including the SecurityMockMvcRequestPostProcessors for OAuth2 in spring-security-test).