Spring/Spring Security

SpringSecurity - Test 작성

개요

Spring Test 를 작성하려고 하다보면, 인증이 필요한 부분이 있습니다.
인증이 필요한 부분에 간단하게 인증을 추가할 수 있는 방법을 소개하겠습니다.
해당 내용은 모두 스프링 공식 문서에서 확인할 수 있습니다.[https://docs.spring.io/spring-security/site/docs/current/reference/html5/#test-method]

Annotation

  • 기본적으로 SecrutityContext 는 method 테스트 직전에 초기화 되는데, 옵션을 통해 테스트 실행시에 한번만 초기화 되도록 할 수 있습니다
    @WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)

@WithMockUser

  • username, roles, authorities 등의 속성을 통해 어떤 사용자를 mocking 할 것인지 설정합니다.
    • roles : 설정한 값에 ROLE_ prefix 를 붙입니다.
    • authorities : prefix 를 붙이지 않습니다.
      @Test
      @WithMockUser(username = "test", roles = {"USER", "ADMIN"})
      void admin() throws Exception {
      mockMvc.perform(get("/admin"))
        .andDo(print())
        .andExpect(status().isOk());
      }

@WithAnonymousUser

  • 익명 사용자 (인증이 필요하지 않은 부분) 를 mocking 합니다

@WithSecurityContext

  • SecurityContext 를 커스텀하게 생성하여 사용하도록 설정합니다

  • 먼저 커스텀한 어노테이션을 생성합니다.(SecurityContext 를 생성할 곳을 마킹하는 어노테이션)

    @Retention(RetentionPolicy.RUNTIME)
    @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
    public @interface WithMockCustomUser {
    String username() default "test";
    
    String name() default "name";
    }
  • WithSecurityContextFactory<커스텀어노테이션> 을 구현하는 factory class 를 정의합니다. (실제 SecurityContext 를 생성해주는 class)

import com.github.prgrms.social.model.user.Email;
import com.github.prgrms.social.model.user.Role;
import com.github.prgrms.social.security.JwtAuthentication;
import com.github.prgrms.social.security.JwtAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.context.support.WithSecurityContextFactory;

import static org.springframework.security.core.authority.AuthorityUtils.createAuthorityList;

public class WithMockCustomUserSecurityContextFactory
  implements WithSecurityContextFactory<WithMockCustomUser> {
  @Override
  public SecurityContext createSecurityContext(WithMockCustomUser annotation) {
    final SecurityContext context = SecurityContextHolder.createEmptyContext();
    // Authentication 은 본인이 인증에 사용하는 클래스를 생성하면 됩니다.
    final Authentication auth = new JwtAuthenticationToken(
      new JwtAuthentication(1L, "tester", new Email("test00@gmail.com")),
      null,
      createAuthorityList(Role.USER.value())
    );

    context.setAuthentication(auth);
    return context;
  }
}
  • 마지막으로 실제 사용방법입니다 어노테이션을 달아줌으로써 해당 메서드는 인증된 정보가 들어있는 SecurityContext 를 받게 됩니다.

    @Test
    @WithMockCustomUser
    void like() throws Exception {
    Post mockPost = new Post.Builder()
      .seq(1L)
      .userId(Id.of(User.class, 1L))
      .writer(new Writer(new Email("test@test.com")))
      .contents("contents")
      .likes(1)
      .likesOfMe(true)
      .build();
    given(postService.like(any(), any(), any())).willReturn(Optional.of(mockPost));
    
    mockMvc.perform(patch("/api/user/1/post/1/like")
           .header("api_key", "Bearer " + token))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(jsonPath("$.response.likesOfMe").value(true));
    }

기타

  • SecurityMockMvcRequestPostProcessors 를 이용한 방법이 있습니다.
  • 아래와 같이 with 메서드의 인자로 custom 하게 사용이 가능합니다. 제공하는 메서드들이 꽤나 많이 있습니다. 궁금하신점은 spring 공식 문서를 참고하면 많은 내용이 있습니다.
    mockMvc.perform(post("/").with(user("user").password("pass").roles("USER")))

실무에서 스프링 시큐리티를 사용하지 않아서, 스터디를 통해 커스텀하게 사용하는 방법과 그에 따라 테스트를 만드는 방법에 대해 공부하는 계기가 되었습니다. 스프링 레퍼런스가 워낙 방대하다보니 내용을 하나하나 찾기가 참 어려운거 같습니다.