Mocking JwtDecoder Spring Security OAuth2

158 Views Asked by At

I've custom annotation, which is used in several controllers:

@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal(expression = "@applicationUserDetailsService.loadUserByUsername(#this.getSubject()).getId()")
public @interface CurrentUserId {

}

Also I've JwtEncoder:

@Bean
    public JwtEncoder jwtEncoder() {
        final JWK jwk = new RSAKey.Builder(keys.getPublicKey()).privateKey(keys.getPrivateKey()).build();
        final JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwkSource);
    }

And JwtDecoder:

@Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(keys.getPublicKey()).build();
    }

My custom static method serves for making mocked calls:

public static ResultActions makeMockMvcRequest(final MockMvc mockMvc,
                                                   final HttpMethod requestType,
                                                   final String endpoint,
                                                   final Map<String, Object> params,
                                                   final Object body,
                                                   final User user) throws Exception {
        final var requestBuilder = defineRequestBuilder(requestType, endpoint);
        addBodyIfExists(body, requestBuilder);
        addUserIfExists(user, requestBuilder);
        return mockMvc.perform(requestBuilder
                .contentType(MediaType.APPLICATION_JSON)
                .params(convertToMultiValueMap(params)));
    }

    private static MockHttpServletRequestBuilder defineRequestBuilder(final HttpMethod requestType, final String endpoint) {
        MockHttpServletRequestBuilder requestBuilder;
        if (POST.equals(requestType)) {
            requestBuilder = post(endpoint);
        } else if (GET.equals(requestType)) {
            requestBuilder = get(endpoint);
        } else if (PUT.equals(requestType)) {
            requestBuilder = put(endpoint);
        } else {
            throw new IllegalArgumentException("Unsupported HTTP request type: " + requestType);
        }
        return requestBuilder;
    }

    private static void addBodyIfExists(final Object body, final MockHttpServletRequestBuilder requestBuilder) {
        if (body != null) {
            requestBuilder.content(asJsonString(body));
        }
    }

    private static void addUserIfExists(final User user, final MockHttpServletRequestBuilder requestBuilder) {
        if (user != null) {
            final String[] roles = user.getRoles().stream()
                    .map(Role::name)
                    .distinct()
                    .toArray(String[]::new);

            final List<GrantedAuthority> authorities = user.getRoles().stream()
                    .map(role -> (GrantedAuthority) role::name)
                    .toList();

            requestBuilder
                    .with(user(user.getEmail()).roles(roles))
                    .with(jwt().jwt(j -> j.subject(user.getEmail()).tokenValue("strong-token").build()).authorities(authorities));
        }
    }

But when I try to make a mock MVC call, I'm getting following error:

jakarta.servlet.ServletException: Request processing failed: org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call method getId() on null context object

How can I mock decoder in order to have resolved given user's ID? Or is there better and more convenient way to achieve mocking goal?

1

There are 1 best solutions below

0
On

However, if someone interested in my approach, then after hours of debugging I've already found a solution:

  1. You have to specify bean for loading user by username:

    @TestConfiguration public class SecurityMockConfiguration {

    @Bean
    public UserDetailsService applicationUserDetailsService() {
        return username -> Stream.of(NEW_USER, FIRST_STUDENT, SECOND_STUDENT, INSTRUCTOR, ADMIN)
                .filter(actor -> Objects.equals(username, actor.getEmail()))
                .findFirst()
                .map(ApplicationUserDetails:: new)
                .orElse(new ApplicationUserDetails(new User()));
    }
    

    }

  2. Add mocked Jwt for you mockMvc:

        if (user != null) {
            final List<GrantedAuthority> authorities = user.getRoles().stream()
                    .map(role -> (GrantedAuthority) () -> "ROLE_" + role.name())
                    .toList();
            requestBuilder.with(jwt().jwt(j -> j.subject(user.getEmail())).authorities(authorities));
        }
    

After that you will be able to make requests based on some user:

makeMockMvcRequest(mockMvc, GET, COURSE_ENDPOINT, FIRST_STUDENT)
                                    .andExpect(responseBody().containsObjectsAsJson(userCourses, UserCourseDto.class))