Как стать автором
Поиск
Написать публикацию
Обновить

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

Уровень сложностиПростой
Время на прочтение3 мин
Количество просмотров1.4K

Как часто вам приходится тестировать аутентификацию в ваших юнит тестах 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 в заголовках запроса

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

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

Теги:
Хабы:
+7
Комментарии4

Публикации

Ближайшие события