[스프링 JPA] 인증된 사용자 정보 : @WithSecurityContext 커스텀 어노테이션 생성
- 프로필 수정 테스트
- 인증된 사용자만 접근할 수 있는 URL이므로 인증된 Authentication 정보가 필요.
- @WithSecurityContext, @WithAccount 를 이용하여 인증된 사용자를 제공하는 커스텀 어노테이션 생성
인증된 사용자만 접근할 수 있는 URL이라면 테스트 코드를 작성할 때 인증된 사용자 정보를 같이 제공해줘야 한다.
프로필 수정하는 테스트 코드를 작성할 때도 인증된 사용자 정보가 필요했다.
먼저 스프링 시큐리티에서 사용자 인증을 위해 다음과 같은 어노테이션이 제공된다.
@WithMockUser 사용자 이름, 패스워드, 권한으로 UserDetails를 생성
@WithUserDetails 지정한 사용자 이름으로 UserDetails 객체를 조회
@WithAnonymousUser 익명 사용자 생성
먼저 @WithUserDetails 를 이용하여 인증된 사용자 정보를 제공해보려고 하였다.
@WithUserDetails(value ="nyju") 는 beforeEach 전에 실행되기 때문에 테스트에 실패 하였다.
@WithUserDetails(value ="nyju", setupBefore = TestExecutionEvent.TEST_EXECUTION)
-> 시큐리티컨텍스트를 설정하는 위치를 지정하는 기능이며 비포다음 Test코드 바로 직전 실행하도록 하였다.
-> But.. 현재 버그가 있어서 그런지 여전히 before실행하기 전에 실행되기 때문에 테스트가 실패하였다.
그래서 @WithSecurityContext 를이용하여 인증된 사용자를 제공할 커스텀 어노테이션을 생성하였다.
@WithSecurityContext 를 이용한 커스텀 어노테이션 생성
// test.WithAccount.java
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithAccountSecurityContextFacotry.class) // 시큐리티컨텍스트를 만들어줄 팩토리를 만듦
public @interface WithAccount {
String value();
}
// test.WithAccountSecurityContextFacotry.java
@RequiredArgsConstructor
public class WithAccountSecurityContextFacotry implements WithSecurityContextFactory<WithAccount> {
private final AccountService accountService;
@Override
public SecurityContext createSecurityContext(WithAccount withAccount) {
String nickname = withAccount.value();
SignUpForm signUpForm = new SignUpForm();
signUpForm.setNickname(nickname);
signUpForm.setEmail(nickname + "@email.com");
signUpForm.setPassword("12345678");
accountService.processNewAccount(signUpForm);
UserDetails principal = accountService.loadUserByUsername(nickname);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication); // 시큐티리 컨텍스트에 넣어줌.
return context;
}
}
위와 같이 커스텀 어노테이션을 생성한 후 @WithAccount를 이용하여 인증된 사용자 정보를 제공할 수 있었다.
@WithAccount("nyju")
@DisplayName("프로필 수정 폼")
@Test
void updateProfileForm() throws Exception {
mockMvc.perform(get(SettingsController.SETTINGS_PROFILE_URL))
.andExpect(status().isOk())
.andExpect(model().attributeExists("account"))
.andExpect(model().attributeExists("profile"));
}
@AfterEach
void afterEach() {
accountRepository.deleteAll();
}
주의 할 점은 WithAccount를 사용할 때마다 계정을 만들기 때문에 afterEach 에서 삭제해주어야 한다.