SpringSecurity 테스트 어노테이션

텐저린티·2023년 9월 18일
0

Spring

목록 보기
4/6
post-thumbnail

이게 뭘까?

Spring Security 프레임워크가 적용된 상태일때 쓰는 테스트용 어노테이션이다.

Spring Security 에서 인증된 사용자를 Mock으로 만들어서 테스트를 수행할 수 있도록 한다.

왜 쓰는 걸까?

상황을 가정해보자.

1. SecurityContextHolder 안 쓰더라도

Spring Security 를 이용해서 프로그램을 돌리는 상황이다.

API 호출할 때 Controller 로 들어오기 전에 Spring Security 필터를 거쳐서 들어오게 된다.

하지만, 테스트 코드를 실행할 때는 인증정보가 들어오지 않는다.

그러면 아무리 API 를 호출해도 인증정보가 없다는 것만 나온다.

구체적으로 이야기하면, 302 응답이 나온다.

인증을 위해서 리다이렉션이 필요하다는 의미다.

Spring Security 에서 permitAll() 로 설정하지 않은 URL 을 제외하고는 모두 테스트 실패가 나온다.

2. SecurityContextHolder 쓴다면

프로그램을 3티어로 구성했다고 하자.

Controller - Service - Repository 이렇게 구성한거다.

이전 예에서는 Controller 에서 문제가 발생한다.

API 호출하는 부분에서 오류가 발생하기 때문.

지금 상황은 Service 레이어에서 SecurityContextHolder.getContext() 를 사용하는 경우다.

테스트 코드에서는 Spring Security 를 거치지 않았기 때문에 SecurityContext 가 null 인 상황이다.

이때 위 어노테이션을 활용해서 SecurityContext 를 Mock 으로 만들어주고 테스트를 돌리는 거다.

무슨 역할?

@WithMockUser

  • 가장 쉬운 방법
  • username, password, roles 을 Mock 으로 넣어줌
  • 결과를 보면 알 수 있듯이, authentication 말고도 principal 에도 넣어준다.
  • 한 마디로 UserDetails 객체를 만들어주는 것
  • SecurityContext 에도 올라간다고 한다.
  • 다만, 직접 만든 Authentication 인증정보는 사용 불가

코드

@Test
@WithMockUser(value = "danaka@naver.com", roles = {"USER"})
void test() {
		// 테스트 코드
}

어떻게 들어오나?

@WithMockUser

@WithUserDetails

  • 위의 어노테이션과의 차이점은 UserDetails 객체를 조회해서 SecurityContext 를 만든다는 점
  • value : 지정한 사용자 이름
  • userDetailsServiceBeanName
    • UserDetails 조회 서비스 빈 이름
    • UserDetailsService 구현체
    • 만약에 구현체가 하나 뿐이라면 생략 가능
  • 결과를 보면, UserDetails 를 조회해서 사용하므로, principal 에도 설정 정보가 들어가 있다.

코드

@Test
@WithUserDetails(value = "danaka@naver.com")
void test() {
		// 테스트 코드
}

어떻게 들어오나?

@WithUserDetails

@WithSecurityContext

  • Authentication 을 커스텀한 경우에 사용한다.
    • 나는 Jwt 사용하면서 커스텀해서 이걸 써야 했다.
  • 서순
    1. @WithSecurityContext 어노테이션이 적용된 커스텀 어노테이션 작성
      1. SecurityContextFactory 를 빈으로 주입받아서 SecurityContext 를 만들어 사용하는 방식
    2. SecurityContextFactory 를 구현한다.
    3. 테스트할 메소드에 어노테이션을 달아준다.

코드

@Test
@WithMockCustomUser
void test() {
		// 테스트 코드
}
public class WithMockCustomUserSecurityContextFactory implements
        WithSecurityContextFactory<WithMockCustomUser> {

    @Override
    public SecurityContext createSecurityContext(WithMockCustomUser mockCustomUser) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();

        JwtAuthenticationToken authentication =
                new JwtAuthenticationToken(
                        new JwtAuthenticationPrincipal(
                                mockCustomUser.email()),
                        null,
                        List.of(new SimpleGrantedAuthority(mockCustomUser.role())));
        context.setAuthentication(authentication);
        return context;
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {

    String email() default "danaka@naver.com";

    String role() default "USER";

}

어떻게 들어오나?

@WithSecurityContext

결론

인증정보를 나만 쓰는게 아니라, 팀원들도 같이 쓰도록 구현하느라 Spring Security 테스트 하는게 꽤 큰 문제가 됐다.

로직에는 인증정보를 사용하도록 했는데, 테스트가 안 되는거니까…

그래도 키워드 잘 얻어서 얼른 찾아보고 적용할 수 있어서 다행이다.

만들어둔 @WithSecurityContext 는 잘 가지고 다니면서 나중에 써먹어야 겠다.

아 참고로, 나는 위 세 개 테스트 어노테이션 중에 마지막 @WithSecurityContext 를 사용했다.

JWTOAuth2 를 사용하면서 Authentication 을 커스텀해서 사용했기 때문.

이 문제 해결하면서, 원래 UserDetails 안 쓰는 방식으로 구현했던 Security 코드로 리팩토링할 수 있어서 좋았다.

profile
개발하고 말테야

0개의 댓글