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?
However, if someone interested in my approach, then after hours of debugging I've already found a solution:
You have to specify bean for loading user by username:
@TestConfiguration public class SecurityMockConfiguration {
}
Add mocked Jwt for you mockMvc:
After that you will be able to make requests based on some user: