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