이슈: 테스트에 필요한 인증정보를 제공하는 @WithMockUser 사용으로 casting Exception 발생하는 문제가 있었습니다.
1차 해결: @WithMockUser 는 기본적으로 인증 타입을 제공하고 있어 해당 이슈가 발생한 것으로 파악되었습니다. 컨텍스트홀더 안에 커스텀한 인증 객체로 덮어쓸 수 있는 메소드를 작성하여 이슈를 해결할 수 있었습니다.
2차 해결: 일차적 해결방법은 매소드를 매번 호출해야 하며 코드 중복인 문제가 있었습니다. 이 점을 개선하고자 커스텀한 AOP를 구성하여 코드중복을 제거하였습니다.
문제점
@WithMockUser 사용으로 인한 ClassCastException 발생
해결
SpringBootTest , DataJpaTest issue[1차 대응]
[상황]
@AutenticationPrincipal이Controller에 setting 되기 전 상황에서 JpaAuditConfig를 설정 한 후 통합테스트 및 JpaTest에서 ClassCastException이 발생했습니다.
[원인 분석]
auditing이 동작하는 매커니즘을 Debuging 해본 결과 authentication 구현체가 UserNameAuthentication 으로 들어가 있어(JwtAuthentication) authentication.getPrinipal(); 이라는 구문에서 ClassCastException이 발생한 것을 확인했습니다.
[해결]
해당 SecurityContextHolder는 Request마다 Thread가 생성되는 방식으로 구성이 되어있으므로 SecurityContextHolder안에 Anonymous AuthenticationToken을 임의로 만들고 contextHolder에 OverWriting 하고 AuditAware에 AnonymousAuth 객체를 필터링 할 수 있게 재구성 하여 해결할 수 있었습니다.
@Before
TestsetUp(){
setMockAnonymousAuthenticationToken();
}
.
.
.
/**
* note : 필요한 데이터를 저장할 때, jpaAudit이 동작하게 된다 (통합테스트에서만)
* @WithMockUser("MEMBER") 사용시 src 내부에 jpaAudit 부분에 castException이 난다.
* 만약 임시 목 유저 데이터를 사용하기 위해서는 해당 메소드를 한번 호출하면 해결된다.
*/
private void setMockAnonymousAuthenticationToken() {
SimpleGrantedAuthority role_anonymous = new SimpleGrantedAuthority("ROLE_MEMBER");
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(role_anonymous);
Authentication authentication = new AnonymousAuthenticationToken("anonymous", "anonymous", authorities);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
다시 SpringBootTest, WebMvcTest중 issue 발생 [2차 대응]
[상황]
Controller 단에 @AutenticationPrincipal을 매개변수로 선언하고 난 후@WebMvcTest ClassCastException이 발생하였으며 @SpringBootTest 또한 Service단의 매개변수인 authId가 null인 상태로 호출 되어 NullPointerException 또한 발생했습니다.
[원인분석]
@withMockUser를 사용하면 해당 구현체 코드에서는 이미 기본적으로 UsernamePasswordAuthenticatioToken 구현체로 MockUser를 생성하는 것을 확인했습니다.
기본적으로 Authentication 객체 구현체는 총 8가지로 구성되어 있으며 그 중 하나는 Custom한 인증객체 이고 나머지는 기본적으로 가지고 있는 구현체 들입니다.
[해결]
그러므로 AnonymousAuthentication 객체로 Overwrite 하는 방식이 아닌 저희가 Custom한 JwtAuthentication으로 인증 주체를 ContextHolder 에 담도록 메소드를 재구성하였습니다.
결과적으로 @withMockUser를 사용하면서 castException도 발생하지 않을 뿐더러, 인증주체에 대한 id 매개변수를 넘길때에도 NullPointerException이 발생하지 않았습니다.
/**
* note : 기존에 통합테스트에서 제공했던 것들이 @AuthenticationPrincipal 이 붙으면서 JwtAuthentication 토근으로 변환해야한다.
* resolve : 통합 테스트에서는 Application Context 환경에 실행 환경과 동일하기 때문에
*/
private void setMockingAuthentication(Long authId) {
SimpleGrantedAuthority role_anonymous = new SimpleGrantedAuthority("ROLE_MEMBER");
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(role_anonymous);
JwtAuthentication jwtAuthentication = new JwtAuthentication("random-token", String.valueOf(authId));
Authentication authentication = new JwtAuthenticationToken(jwtAuthentication, "anonymous", authorities);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
[문제점] : 테스트 코드 작성시 인증 객체를 위한 setMockingAuthentication() 호출 중복
[상황]
test 코드에 매번 해당 코드를 내부적으로 복사해서 @Before Method로 호출하거나 MockUser가 필요한 일부분의 테스트 코드에는 직접 호출하는 상황이 발생했다.
즉, 기본적으로 코드의 복사 붙여넣기의 문제와 코드가 중복되어 작성하는 문제가 발생했습니다.
code
테스트 코드 클래스 부분 도식화
[해결]
WithMockJwtAuthenticationSecurityContextFactory 재구성하여 불필요한 코드 중복으로부터 벗어날 수 있었습니다.
@WithMockJwtAuthetentication 의 사용으로 반복적인 메소드 호출을 피하고 메소드 상단에 표시하거나 혹은 클래스 상단에 선언함으로써 코드가 깔끔해 졌습니다