Test / Spring Security / 단위 테스트에 Security 추가하기
Spring Security / 단위 테스트에 Security 추가하기
프로젝트에 Spring Security를 적용하면 전에는 성공하던 테스트가 실패하게 된다. Spring Security가 인증을 받지 못한 사용자의 접근을 막기 때문이다.
그럼 어떻게 인증된 사용자를 만들어 테스트를 성공할 수 있을까?
Spring Security는 이를 위해 여러 애노테이션을 제공한다.
1. @WithMockUser
@Test
@WithMockUser
void getMessageWithMockUser() {
String message = messageService.getMessage();
...
}
@WithMockUser 애노테이션을 붙인 테스트 메서드는 username : "user", password : "password"이며, role이 "ROLE_USER"인 인증된 유저로 테스트가 실행된다.
@WithMockUser 애노테이션으로 인증된 유저를 생성하려면 다음과 같은 조건이 있다.
- "user"라는 이름을 가진 유저는 실제로 없어도 된다.
- UsernamePasswordAuthenticationToken 타입의 인증 객체가 SecurityContext에 생성된다.
- 인증 객체 내의 principal은 Spring Security의 User 객체여야 한다.
- User 객체는 username이 "user", password가 "password", role이 "ROLE_USER"이다.
만약 다른 username, role을 갖게 하고 싶으면 다음과 같이 작성하면 된다.
@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
void getMessageWithMockUserCustomUser() {
String message = messageService.getMessage();
...
}
테스트 클래스에 붙여 속한 모든 테스트 메서드에 적용시킬 수 있다.
@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public void getMessageWithMockUserCustomUser() {
String message = messageService.getMessage();
...
}
주의할 점은 JWT와 같은 커스텀 filter를 생성해 인증(Authentication) 객체를 생성하면 @WithMockUser 애노테이션을 사용할 수 없어, 커스텀한 애노테이션을 만들어야 한다.
2. @WithAnonymousUser
익명의 사용자로 테스트를 하고 싶을 때 사용한다.
@Test
@WithAnonymousUser
void anonymous() throws Exception {
}
3. @WithUserDetails
커스텀한 principal, UserDetailsService를 통해 인증(Authentication) 객체를 생성하면 @WithMockUser 애노테이션을 사용할 수 없으므로, @WithUserDetails 애노테이션을 사용해 커스텀한 userDetailsService 빈 이름을 명시해 테스트 사용자를 만들어야 한다.
@Test
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
public void getMessageWithUserDetailsCustomUsername() {
String message = messageService.getMessage();
...
}
위 테스트는 myUserDetailsService라는 이름의 UserDetailsService 빈을 사용해서 사용자 이름(username)이 customUsername인 사용자를 찾는다.
@WithUserDetails 애노테이션은 @WithMockUser 애노테이션과 다르게 실제로 사용자가 있어야 한다.
따라서 JUnit의 @Before 애노테이션을 붙인 테스트 메서드에서 사용자를 등록한 후 위 테스트를 실행시켜야 한다.
이때 주의할 점이 있다. 기본적으로 SecurityContext에 테스트 사용자를 등록하는 이벤트는 @Before 애노테이션 전에 실행된다. 따라서 @Before 실행 후, 테스트 메서드 실행 전에 테스트 사용자를 등록할 수 있도록 다음과 같이 설정해야 한다.
@WithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION)
4. @WithSecurityContext
위에서 말했듯이 커스텀한 principal, UserDetailsService을 사용해 인증(Authentication) 객체를 만들면 @WithMockUser 애노테이션을 사용할 수 없다.
그럼 @WithUserDetails 애노테이션을 사용해야 할까? 이 방법은 테스트 사용자가 실제로 존재해야 해서 테스트가 복잡해질 수 있다.
따라서 직접 SecuritContext를 생성해주는 @WithSecurityContext를 사용해 직접 애노테이션을 만들어야 한다. 이 방법이 가장 유연하게 사용할 수 있는 옵션이다.
아래와 같은 @WithMockCustomUser 애노테이션을 생성할 수 있다.
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {
String username() default "rob";
String name() default "Rob Winch";
}
위 코드를 보면 WithMockCustomUser에는 @WithSecurityContext 애노테이션을 사용했다. 이는 SecuriyContext를 생성해서 테스트를 실행하라는 신호로 받아들인다.
테스트 메서드에 @WithMockCustomUser 애노테이션을 선언했을 때 SecuriyContext에 인증(Authentication) 객체를 저장할 방식을 정해야 한다.
WithMockCustomUserSecurityContextFactory 구현은 아래와 같다.
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
@Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
CustomUserDetails principal =
new CustomUserDetails(customUser.name(), customUser.username());
Authentication auth =
new UsernamePasswordAuthenticationToken(principal, "password", principal.getAuthorities());
context.setAuthentication(auth);
return context;
}
}
Spring Security 공식 문서에 작성된 예시이므로 본인의 방식에 맞춰 커스텀하게 구현하면 된다.
5. 참고
'개발 > Test' 카테고리의 다른 글
Test / JUnit5 / 테스트를 위한 애노테이션 (0) | 2021.06.15 |
---|
댓글