본문 바로가기

spring/security

Spring Security Test 작성하기

들어가면서

스프링 시큐리티를 적용하면 각 EndPoint를 접근할 때, 권한 인증은 어떻게 고려해서 테스트를 작성해야 하는지 고민이라면 읽어보는 것을 추천한다.

가짜 인증 만들어 인증/인가 단계 처리하기

시큐리티 테스트를 위해서 제공하는 어노테이션을 활용하면 임시로 인증 상태로 만들 수 있다.

1.@WithMockUser

시큐리티의 UsernamePasswordAuthenticationToken을 만들어서 주입해주는 어노테이션이다.
쉽게 말해서 인증된 유저를 설정하는 기능을 제공한다.

@Test
@WithMockUser
public void getMessageWithMockUser() {
String message = messageService.getMessage();
...
}

아무런 설정없이 사용하면 인증된 유저임을 나타내는 식으로 사용할 수 있고

@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public void getMessageWithMockUserCustomUser() {
    String message = messageService.getMessage();
    ...
}

인증된 유저의 이름과 역할(Role)을 설정할 수도 있다.

@Test
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
public void getMessageWithMockUserCustomAuthorities() {
    String message = messageService.getMessage();
    ...
}

역할이 아니라 권한을 지정하는 방식도 가능하다.

2.@WithAnonymousUser

익명의 유저를 생성하는 어노테이션이다. 특별한 기능적인 차이는 없고 그냥 이름에 익명임을 명시하여 의도를 드러내기 좋은 어노테이션이다.

3.@WithUserDetails

만약에 UserDetailsService에서 커스텀한 Principal을 반환하는 식으로 서비스를 개발했다면 @WithMockUser를 사용하기에는 곤란하다. 그런 경우에 사용하는 것이
이 어노테이션이다. 이 방식은 UserDetailsService에 의존적인 유저 생성 기능임을 주의해야 한다. 만약 UserDetailsService에서 오류가 나면 해당 테스트는 작동하지 않을 수 있다!

@Test
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
public void getMessageWithUserDetailsServiceBeanName() {
    String message = messageService.getMessage();
    ...
}

loadByUsername(String username) 메서드에 쓰이는 username을 value에 입력하는 것이고 userDetailsServiceBeanName은 만약 커스텀한 서비스가 있다면 등록하는 로직이다.

4. @WithSecurityContext

위의 방법은 시큐리티의 매커니즘을 활용한 기능들이기 때문에 유연성은 떨어진다. 그래서 좀 더 유연하게 어떤 인증 로직이더라도 SecurityContext를 사용한다면
테스트를 할 수 있도록 커스텀을 할 수 있다. 이때 사용하는 것이 바로 @WithSecurityContext이다.

커스텀 절차!
1. 커스텀 모의 인증 객체 선언 어노테이션 만들기
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {

    String username() default "rob";

    String name() default "Rob Winch";
}

커스텀 어노테이션에 사용할 팩터리 클래스를 매핑한다.

2. 커스텀 Authentication 생성 로직을 구현하는 팩토리 클래스 만들기
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 =
            UsernamePasswordAuthenticationToken.authenticated(principal, "password", principal.getAuthorities());
        context.setAuthentication(auth);
        return context;
    }
}
3. @WithMockCustomUser를 이용하여 테스트 작성하기

RequestPostProcessor를 이용한 가짜 유저(인증) 생성

어노테이션 기반으로 가짜 유저(인증)을 만드는 것도 가능하지만 MockMvc에서는 테스트를 하기 위한 기능을 제공한다. 그 역할은 앞에서 배운 것과 동일하다.

1.@WithMockUser 기능과 같은 코드

mvc.perform(get("/admin")
        .with(user("admin")
        .password("pass")
        .roles("USER","ADMIN")))

2.@WithAnonymousUser기능과 같은 코드

mvc.perform(get("/")
        .with(anonymous()))

3. Authentication 주입하여 인증하는 코드

mvc.perform(get("/")
        .with(authentication(authentication)))

4. @WithSecurityContext기능과 같이 컨텍스트 주입하여 인증하는 코드

mvc.perform(get("/")
        .with(securityContext(securityContext)))

Reference

 

Testing Method Security :: Spring Security

If you reuse the same user within your tests often, it is not ideal to have to repeatedly specify the attributes. For example, if you have many tests related to an administrative user with a username of admin and roles of ROLE_USER and ROLE_ADMIN, you have

docs.spring.io