Красиво инжектим JwtAuthenticationToken в Spring Boot юнит тесты

от автора

Как часто вам приходится тестировать аутентификацию в ваших юнит тестах Spring Boot приложений? Мне довольно часто.

Я работаю с oauth2 реализацией от Spring Security и в сервисах подключен стартер spring-boot-starter-oauth2-resource-server.

Каждый раз, когда мне требуется протестировать какой либо класс или метод, где приходится работать с SecurityContextHolder и доставать JwtAuthenticationToken из контекста, я смотрю на способы, с помощью которых я это делаю и мне не нравится реализация.

И я начинаю переписывать, и переписывать и переписывать… И кажется, сейчас я подобрал самый удобный и лаконичный способ и делюсь им с вами.

Давайте перейдем к делу!

Для возможности корректного тестирования аутентификации добавим в зависимости библиотеку spring-security-test:

Maven
<dependency>     <groupId>org.springframework.security</groupId>     <artifactId>spring-security-test</artifactId>     <scope>test</scope> </dependency>
Gradle
testImplementation 'org.springframework.security:spring-security-test'

В первую очередь создаю аннотацию WithMockJwtAuthentication:

@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @WithSecurityContext(factory = WithMockJwtAuthenticationSecurityContextFactory.class) public @interface WithMockJwtAuthentication {      String subject() default "user@example.com";      Claim[] claims() default {};      int expiresInSeconds() default 300;      @AliasFor(annotation = WithSecurityContext.class)     TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;      @interface Claim {          String name();          String value();      }  }

И добавляю класс WithMockJwtAuthenticationSecurityContextFactory:

public class WithMockJwtAuthenticationSecurityContextFactory implements WithSecurityContextFactory<WithMockJwtAuthentication> {      private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();      @Override     public SecurityContext createSecurityContext(WithMockJwtAuthentication annotation) {         Instant issuedAt = Instant.now();         Instant expiresAt = issuedAt.plusSeconds(annotation.expiresInSeconds());          Map<String, Object> claims = new HashMap<>();         for (WithMockJwtAuthentication.Claim claim : annotation.claims()) {             claims.put(claim.name(), claim.value());         }          Jwt jwt = Jwt.withTokenValue("token")                 .header("alg", "none")                 .subject(annotation.subject())                 .issuedAt(issuedAt)                 .expiresAt(expiresAt)                 .claims(it -> it.putAll(claims))                 .build();          Authentication authentication = new JwtAuthenticationToken(jwt);         SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();         context.setAuthentication(authentication);         return context;     }  }

На этом все. Если не требуется расширять текущий вариант, то можно переходить к подключению аннотации в тестах…

Добавление аннотации @WithMockJwtAuthentication к тестовому методу:

Попробуем продебажить тест и убедиться, что действительно в security context попадает наш токен аутентификации:

Выводы:

  • Я получил удобный способ внедрять JwtAuthenticationToken в security context как если бы мой сервис принимал bearer token в заголовках запроса

  • Вы можете расширить мой пример под свои нужды, упаковать в свою библиотеку и удобно подключать к своим сервисам

А как вы работаете с аутентификацией в юнит тестах?


ссылка на оригинал статьи https://habr.com/ru/articles/931476/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *