[Pet-Hub] Spring RestDocs 문서화를 위한 Custom Security 인증객체 주입하기

DevSeoRex·2023년 6월 18일
5
post-thumbnail

😲 해결해야 하는 요구사항

API 문서화를 진행하기 위해, Spring Rest Docs를 이용한 문서 생성을 진행하고 있었는데 Spring Rest Docs는 모든 테스트가 통과해야 문서가 생성되어 테스트를 강제하는 장점이 있습니다.

Pet-Hub 서비스는 Spring Security 기반의 JWT 인증을 통한 회원 서비스가 구현되어 있습니다.
JWT 토큰은 만료기간이 정해져있고, 토큰이 만료되면 JWT 만료로 인한 예외가 발생하면서 Spring Rest Docs 테스트에서도 문제가 발생하게됩니다.

Spring Rest Docs 문서화를 위한 테스트코드에서 문제가 생기지 않도록 핸들링해줘야 하는 요구사항은 아래와 같습니다.

  1. SecurityContext에 인증객체(Authentication)를 넣어줘야 한다.
  2. JWT 인증기능을 Test 코드 동작시에는 수행하지 않도록 해야한다.

이제 이 요구사항들을 어떻게 만족시키면서 Spring Rest Docs 문서화를 위한 테스트 코드를 작성할지 고민하며 문제를 해결해 보겠습니다.

🙆 어떻게 해결하면 될까?

가장 먼저 해야할 일은 JWT 인증을 수행하는 JwtAuthFilter와 Spring Security 설정 클래스인 SecurityConfig 클래스를 Test 환경에서는 Bean으로 등록하지 않도록 하는 것입니다.



@Profile 애너테이션을 이용해서 local(배포) 환경에서만 Bean으로 등록될 수 있게 설정하여, Test 환경과 Security 설정을 분리해주었습니다.

Test 환경에서 작동할 application.yml 파일도 작성하여, test 프로필로 동작하도록 설정하였습니다.

Security에 대한 설정없이 작성된 테스트 코드를 실행해보겠습니다.

NullPointerException이 발생하는 것을 볼 수 있습니다. 왜 발생했을까요?

현재 Rest Docs 문서화를 위해 수행하는 Test는 MockMvc를 이용한 WebMvcTest입니다.
분양글을 등록하는 Controller에서 Security Context 내부의 인증 객체를 꺼내서 사용하는 코드가 있는데 현재 Security Context의 Authentication 객체가 null 이기 때문에 발생하는 문제입니다.

@WithMockUser를 사용

@WithMockUser 애너테이션을 사용하면 UserDetails를 생성하고 Security Context에 등록해줍니다.
@WithMockUser를 사용하면 문제가 해결되는지 다시 코드를 실행해보겠습니다.


이번에는 NPE(NullPointerException)이 발생하지는 않았지만, ClassCastException이 발생했습니다.
그 이유는 Security 관련 기능을 개발할때, Authentication 내부의 principal에 사용자 custom 인증객체를 넣어주었기 때문입니다.

SecurityUtils의 인증객체를 추출하는 부분을 살펴보겠습니다.

Controller에서 SecurityUserDto를 Authentication 객체 내부의 principal로 부터 가져와서 사용하게 되는데, 현재 Authentication 내부에 들어있는 객체는 Security Package의 User 클래스이기 때문에 ClassCastException이 발생합니다.

그렇다면 어떤 Authentication 객체가 Security Context에 들어있는지 콘솔에 찍어보겠습니다.

User 클래스의 객체가 들어있습니다. 즉, @WithMockUser를 사용하면 사용자 custom 인증객체를 사용하였을때는 문제가 발생해서 테스트를 수행할 수 없습니다.

@WithSecurityContext을 이용한 Custom Annotation 사용

Security Context를 생성하고, 내부에 인증객체를 직접 넣어줄 수 있는 로직을 작성하기 위해서 커스텀 애너테이션을 활용해 문제를 해결해보겠습니다.

가장 먼저 WithSecurityContextFactory<A extends Annotation>을 구현한 클래스를 작성하겠습니다.


주목해서 보셔야 할 부분은 Authentication의 내부에 있는 principal 필드에 SecurityUserDto를 넣어주고 있는 부분입니다.

SecurityContext를 직접 생성하고, 내부에 인증객체를 직접 셋팅해주는 것까지 직접 코드로 작성하여 Custom 인증 객체를 사용할때도 Test 수행에 문제가 되지 않도록 해결할 수 있습니다.

Test 메서드에 붙여줘야 하는 WIthMockCustomAccount 애너테이션 인터페이스를 작성하겠습니다.

이제 @WithMockUser를 @WithMockCustomAccount로 바꾸고 테스트 코드를 수행해보겠습니다.

Test가 성공하고 UsernamePasswordAuthenticationToken 내부의 principal 필드에 SecurityUserDto 객체가 할당되어 있어서 ClassCastException이 발생하지 않은 것을 확인할 수 있습니다.

🧐 문제를 해결하며 느낀점

Spring Rest Docs 문서화 작업을 위해 테스트 코드를 작성했는데, 기존에 작성한 테스트 코드들은 모두 단위 테스트 형식으로 작성되어 Presentation Layer와 별개로 기능만 테스트 되었기 때문에 Spring Security로 인한 문제가 발생하지 않았습니다.

Spring Rest Docs로 문서화를 하는 이유는 API 명세를 문서화하려고 하는 것인데, Spring Security의 기능을 테스트 하는 것이 아닌, 컨트롤러에 바인딩해야 하는 필수 파라미터들을 바인딩했을때 원하는 출력값이 나오고 있는지 확인하는 것이 중요하다는 판단이 들었습니다.

따라서 배포 환경에서 사용되는 Security 설정 클래스들을 테스트 환경에서 동작하지 않도록 설정하였고, Custom 인증객체를 SecurityContext를 생성하여 넣어주어서 문제를 해결하였습니다.

테스트의 목적에 따라서 어떤 환경을 구성하고, 원하는 목적에 부합하는 동작을 하는지 테스트 해야겠다는 생각이 드는 문제였습니다.

오늘도 읽어주셔서 감사합니다.

🙇

0개의 댓글