[프로젝트] RestDocs 커스텀 유저 모킹 `@MockMember` 적용기

개요

  • @MockMember 어노테이션의 경우
    • 테스트 환경에서 시큐리티에서 인증된 사용자의 모의(Mock) 하기 위해서 사용된다.
  • 일반적인 경우에는 @WithMockUser 등의 사용자 모킹을 하면 되겠지만..
  • 나는 @WithMockUser 가 사용을 해도 일단 인증을 받아 올 수가 없었다.
  • 그래서 검색하던 중에 발견한 것이 별도의 커스텀 어노테이션을 통하여 SecurityContext 안의 보안 객체를 커스텀하게 테스트코드에서만 채우는 방법이 있다는 것을 발견했다.

어노테이션 코드

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = MockMemberSecurityContextFactory.class)
public @interface MockMember {
	String email() default "mockMember3410@gmail.com";
	
	Member.Role role() default Member.Role.ROLE_CUSTOMER;
	
	Member.Status status() default Member.Status.ACTIVE;
}
  • Retention
    • 런타임으로 설정
    • 어노테이션이 런타임단계에서도 유효해야 테스트가 가능하기에 설정
  • WithSecurityContext
    • 별도의 커스텀된 SecurityContextFactory 를 탈 수 있도록 설정이 가능한 어노테이션이다.
    • 테스트 중에 Security Context 를 쉽게 모의할 수 있도록 도와주는 어노테이션이다.
    @Target({ ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface WithSecurityContext {
    
    	/**
    	 * The {@link WithUserDetailsSecurityContextFactory} to use to create the
    	 * {@link SecurityContext}. It can contain {@link Autowired} and other Spring
    	 * annotations.
    	 * @return
    	 */
    	Class<? extends WithSecurityContextFactory<? extends Annotation>> factory();
    
    	/**
    	 * Determines when the {@link SecurityContext} is setup. The default is before
    	 * {@link TestExecutionEvent#TEST_METHOD} which occurs during
    	 * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
    	 * @return the {@link TestExecutionEvent} to initialize before
    	 * @since 5.1
    	 */
    	TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
    
    }
    • 기본적으로 테스트메서드에 초점이 맞춰져있는 어노테이션임을 알 수 있다.

MockMemberSecurityContextFactory

public class MockMemberSecurityContextFactory implements WithSecurityContextFactory<MockMember> {
	
	@Override
	public SecurityContext createSecurityContext(MockMember annotation) {
		Jwt jwt = Jwt.withTokenValue("token")
			.header("alg", "hs-256")
			.claim("sub", annotation.email())
			.build();
		
		JwtAuthenticationToken authentication = new JwtAuthenticationToken(jwt, null, null);
		
		SecurityContext context = org.springframework.security.core.context.SecurityContextHolder.createEmptyContext();
		
		context.setAuthentication(authentication);
		
		return context;
	}
}

팩토리 메서드상의 상속의 근거

@Target({ ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface WithSecurityContext {

	/**
	 * The {@link WithUserDetailsSecurityContextFactory} to use to create the
	 * {@link SecurityContext}. It can contain {@link Autowired} and other Spring
	 * annotations.
	 * @return
	 */
	Class<? extends WithSecurityContextFactory<? extends Annotation>> factory();
  • 위 WithSecurityContext 상에서 Class 의 타입은 WithSecurityContextFactory 의 서브 클래스이여 함을 알 수 있다. ⇒ ? extends WithSecurityContextFactory
  • 위 정보를 토대로
public class MockMemberSecurityContextFactory implements WithSecurityContextFactory<MockMember> {
  • 를 설정할 수 있는 근거가 발생한다.
  • WithSecurityContextFactory 의 < 내 커스텀 어노테이션 extends Annotation > 이 성립

JWT AUTHENTICATION TOKEN 의 테스트 모킹

  • 기본적으로 내 프로젝트에서 oauth2 resource server 라이브러리를 임포트중이다.
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
    • JWT 토큰을 기본 전략으로 삼았기 때문에,
      • 전략의 근거
        @Bean
            public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
                http.cors(); // cors 허용
                ...
                    
                    .oauth2ResourceServer(
                        OAuth2ResourceServerConfigurer::jwt) // oauth2ResourceServer 를 JWT 방식으로 설정
                    
                   ...
            }
        • jwt 방식으로 OAuth2ResourceServer 를 설정함
      • 다른 프로젝트의 일반적인 예시들과 조금 다름.
      • UsernamePasswordAuthenticationToken 말고
      • 항상 JwtAuthenticationToken 을 기본 보안 객체로 받게됨.
  • 그래서..

MockMemberSecurityContextFactory

@Override
	public SecurityContext createSecurityContext(MockMember annotation) {
		Jwt jwt = Jwt.withTokenValue("token")
			.header("alg", "hs-256")
			.claim("sub", annotation.email())
			.build();
		
		JwtAuthenticationToken authentication = new JwtAuthenticationToken(jwt, null, null);
		
		SecurityContext context = org.springframework.security.core.context.SecurityContextHolder.createEmptyContext();
		
		context.setAuthentication(authentication);
		
		return context;
	}
  • 이런식으로 모의 JWT 객체를 만들고
  • JwtAuthenticationToken 을 만들어 보안 컨텍스트에 채워넣어줘야
  • 정상적인 JWT 엑세스 토큰을 통한 인증 → 로직 → 반환 의 일련이 흐름이 가능하다.

Uploaded by N2T