본문 바로가기

STUDY/Spring

Spring Boot | Spring Security Test @WithMockUser를 커스터마이징 해서 사용하자

스프링 시큐리티가 적용되어 있는 프로젝트에서 컨트롤러 통합 테스트 작성 중.. @WithMockUser만으로 해결되지 않는 경우가 발생했다.

@WithMockUser

가장 쉽게 특정 유저를 설정해 테스트를 진행하는 방법이다. username, password, roles 등 항목을 입력할 수 있다.
아래와 같이 사용하면, test라는 유저명을 가진 가짜 유저를 생성한다(mocking user)
물론 입력한 값들이 사용된 AuthenticationSecurityContext에도 올라간다.

@WithMockUser(username = "test", roles = "USER")
@Test
void withMockUserTest() { ... }

하지만 커스텀된 Authentication 인증 정보는 사용할 수 없다.
@WithUserDetails라는 애노테이션도 있지만 UserDetailsService를 사용해 Authentication을 만드는 경우에만 해당된다.

UserDetailsService는 오직 loadUserByUsername()메서드만 갖고 있고, UserDetails 형식만 반환 가능하다

좀 더 유연하게 원하는 Authentication을 사용하고자 한다면 @WithSecurityContext를 사용해야 한다.

@WithSecurityContext

우선 애노테이션을 만들어준다.
애노테이션 생성 시 @WithSecurityContext애노테이션을 붙여주어야 한다.
@WithSecurityContext 애노테이션은 스프링 시큐리티 테스트용 SecurityContext를 만들겠다는 사인이라고 생각하면 된다. factory라는 값을 필수로 입력해야 하는데, 해당 클래스에서 우리가 만든 @WithMockCustomUser애노테이션에게 새로운 SecurityContext를 생성해 전달해야 한다.

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {

    String username() default "foo";

    String grade() default "ADMIN";

}

위에 @WithSecurityContext애노테이션에 전달한 WithMockCustomUserSecurityContextFactory클래스를 만든다.
이 클래스는 WithMockCustomUserSecurityContextFactory 인터페이스를 구현하여 작성한다.

public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {

    @Override
    public SecurityContext createSecurityContext(WithMockCustomUser annotation) {

        final SecurityContext securityContext = SecurityContextHolder.createEmptyContext();

        final UsernamePasswordAuthenticationToken authenticationToken
                = new UsernamePasswordAuthenticationToken(annotation.username(),
                "password",
                Arrays.asList(new SimpleGrantedAuthority(annotation.grade())));

        securityContext.setAuthentication(authenticationToken);
        return securityContext;
    }

}

이제 @WithMockCustomUser애노테이션을 사용할 수 있다.
default 값이 모두 있으므로 아무 값을 입력하지 않고도 사용할 수 있다.

    @WithMockCustomUser
    @Test
    void getUserList() throws Exception {
        ResultActions perform = this.mvc.perform(get("/users")
                .param("page", "0")
                .param("size", "20"));
        //...생략
    }