문제
- Controller 테스트 코드를 만드는 도중에 현재 로그인 된 유저가 존재해야 하는데 해당 유저가 존재하는 상태를 넘겨주는 것을 어떻게 하는지 몰라서 많이 헤맸다.
- 회원 가입을 할 때에는 그저 정보를 넘겨주면 되었는데, 다른 기능들 (유저 프로필 변경, 로그 아웃, 프로필 보기 등)을 할 때에는 현재 로그인 된 유저, 다시 말하면 인증된 유저 값이 필요했다.
- 테스트 코드 작성은 처음이라서 어떻게 하는지 몰라 거의 하루 다 쓴듯...
시도
- 해당 문제를 해결하기 위해서 사용하는 방법이 여러가지 있는데 그 중에서 @WithMockCustomUser라는 가짜 유저를 만들어서 사용하는 방법이 있어서 사용해보았다.
- 해당 방법은 custom 어노테이션을 만들어서 하는 방법으로 보이는데, 이것만 만들어서 되는 것이 아니라 WithMockCustomUserSecurityContextFactory라는 클래스를 따로 만들어서 인증 절차를 진행해야 했다.
- 해당 클래스 안에는 createSecurityContext라는 WithSecurityContextFactory에서 override된 메서드가 존재하는데 해당 메서드에서 authentication을 만들어서 넘겨주는 방식이었다.
- 여기에서 또 문제가 생겼는데 이렇게 만든 Mock 유저를 넘겨 주었는데도 Authentication이 null이라고 나왔다.
해결
- 해결하는 방법은 다음과 같았다.
- createSecurityContext에서 내가 사용한 방법은 UserDetails를 만들어서 해당 userdetails안에 WithMockCustomUser에서 default로 설정한 유저를 넣어주는 방식이었다.
- 하지만 이 방법이 아니라 UserDetails안에 UserDetailsService에 존재하는 loadUserByUsername 메서드에 WithMockCustomUser의 username을 집어넣고, 이를 UsernamePasswordAuthenticationToken.authenticated에 할당하는 방법을 사용하자 제대로 SecurityContext에 setAuthentication이 작동하여 Authentication이 null이 아니게 되었다.
해당 코드들
package com.winner.trelloimplementation;
import org.springframework.security.test.context.support.WithSecurityContext;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {
String username() default "testuser";
String nickname() default "testnick";
String password() default "testpass";
String email() default "test@test.com";
String introduction() default "testintro";
}
package com.winner.trelloimplementation;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
private final UserDetailsService userDetailsService;
public WithMockCustomUserSecurityContextFactory(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
public SecurityContext createSecurityContext(WithMockCustomUser annotation) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
UserDetails principal = userDetailsService.loadUserByUsername(annotation.username());
Authentication auth = UsernamePasswordAuthenticationToken.authenticated(principal, principal.getPassword(), principal.getAuthorities());
context.setAuthentication(auth);
return context;
}
}
좋은 글 감사합니다. 자주 올게요 :)