제육's 휘발성 코딩
article thumbnail
반응형

@WithMockUser 

@WithMockUser 어노테이션은 Controller 테스트 시에 Spring Security에 설정한 인증 정보를 제공해 주는 역할을 합니다. 

정확히는 사용자 인증 정보를 담은 Authentication을 UsernamePasswordAuthenticationToken으로 넣어주고, Principal은 User 객체에 넣어 SecurityContext에 보관해 준다. 어떻게 적용되는 것인지 살펴보자.

 

@SpringBootTest
@AutoConfigureMockMvc
public class PostControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private PostService postService;

    @Test
    @WithMockUser()
    void 포스트작성() throws Exception{

        String title = "title";
        String body = "body";

        mockMvc.perform(post("/api/v1/posts")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new PostCreateRequest(title, body)))
                ).andDo(print())
                .andExpect(status().isOk());
    }
  • @WithMockUser 어노테이션을 사용하면 다음과 같이 간단하게 인증된 사용자를 만들지 않아도 테스트를 요청할 수 있다. 어떻게 된 영문인지 어노테이션을 살펴보자.

 

Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(factory = WithMockUserSecurityContextFactory.class)
public @interface WithMockUser {

	String value() default "user";

	String username() default "";

	String[] roles() default { "USER" };

	String[] authorities() default {};

	String password() default "password";
  • @WithMockUser 어노테이션에는 username, password, role에 대한 설정이 default로 잡혀있다. 

 

final class WithMockUserSecurityContextFactory implements WithSecurityContextFactory<WithMockUser> {
	@Override
	public SecurityContext createSecurityContext(WithMockUser withUser) {
    	// ...
        User principal = new User(username, withUser.password(), true, true, true, true, grantedAuthorities);  
        Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(),
                principal.getAuthorities());
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);
        return context;
    }
}
  • default 또는 사용자가 지정한 username, password, role의 정보를 UserPasswordAuthenticationToken과 Principal에 담아서 Spring Security Context에 담아준다. 

 

 

@Test
@WithMockUser()
void 포스트작성() throws Exception{

    String title = "title";
    String body = "body";

    SecurityContext context = SecurityContextHolder.getContext();
    context.getAuthentication();

    mockMvc.perform(post("/api/v1/posts")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsBytes(new PostCreateRequest(title, body)))
            ).andDo(print())
            .andExpect(status().isOk());
}
  • 테스트 코드 중간에 SecurityContext를 가져와서 Authentication에 대한 정보를 디버깅해보자.

 

Security Context Debug

  • authentication에 default 값인 user, password, ROLE_USER 가 들어가 있음을 확인할 수 있다. 

 

@WithAnonymousUser

@WithAnonymousUser는 @WithMockUser의 반대의 의미로 인증되지 않은 사용자를 만들어서 사용해 준다.

@WithMockUser에서 사용된 UsernamePasswordAuthenticationToken이 아닌 AnonymousAuthenticationToken을 Authentication에 담아서 보내준다. 

 

@Test
@WithAnonymousUser
void 포스트작성시_로그인하지않은경우() throws Exception{

    String title = "title";
    String body = "body";

    mockMvc.perform(post("/api/v1/posts")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsBytes(new PostCreateRequest(title, body)))
            ).andDo(print())
            .andExpect(status().isUnauthorized());
}
  • 사용 방법은 동일하며, 의미만 반대의 역할을 한다. 

 

@WithUserDetails

사용자 정보가 @WithMockUser나 @WithAnonymousUser와 동일한 경우는 바로 사용하면 되지만, 추가적인 정보가 있다면 UserDetailsService를 구현한 서비스에서 사용자 정보를 load 하도록 설정해 줄 수 있다. 

 

@Override
public SecurityContext createSecurityContext(WithUserDetails withUser) {
    String beanName = withUser.userDetailsServiceBeanName();
    UserDetailsService userDetailsService = findUserDetailsService(beanName);
    String username = withUser.value();
    Assert.hasLength(username, "value() must be non empty String");
    UserDetails principal = userDetailsService.loadUserByUsername(username);
    Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(),
            principal.getAuthorities());
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    context.setAuthentication(authentication);
    return context;
}
  • @WithUserDetails 어노테이션을 살펴보면 직접 구현한 UserDetailService를 찾아 사용 정보를 Principal로 설정해준다. 물론 User 객체도 UserDetails를 구현해야 한다. 

 

REFERENCE

https://tecoble.techcourse.co.kr/post/2020-09-30-spring-security-test/

 

반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^