Издевательский JwtDecoder Spring Security OAuth2
У меня есть собственная аннотация, которая используется в нескольких контроллерах:
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal(expression = "@applicationUserDetailsService.loadUserByUsername(#this.getSubject()).getId()")
public @interface CurrentUserId {
}
Также у меня есть 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);
}
И JwtDecoder:
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(keys.getPublicKey()).build();
}
Мой собственный статический метод служит для создания имитируемых вызовов:
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));
}
}
Но когда я пытаюсь сделать имитацию вызова MVC, я получаю следующую ошибку:
jakarta.servlet.ServletException: Request processing failed: org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call method getId() on null context object
Как я могу имитировать декодер, чтобы разрешить данный идентификатор пользователя? Или есть лучший и более удобный способ достичь издевательской цели?
1 ответ
Однако если кого-то заинтересует мой подход, то после нескольких часов отладки я уже нашел решение:
Вам необходимо указать bean-компонент для загрузки пользователя по имени пользователя:
@TestConfiguration общественный класс 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())); }
}
Добавьте для себя высмеиваемый Jwt (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)); }
После этого вы сможете делать запросы на основе какого-либо пользователя:
makeMockMvcRequest(mockMvc, GET, COURSE_ENDPOINT, FIRST_STUDENT)
.andExpect(responseBody().containsObjectsAsJson(userCourses, UserCourseDto.class))