Study/Spring JPA

[스프링 JPA] 인증된 사용자 정보 : @WithSecurityContext 커스텀 어노테이션 생성

주닝닝 2021. 3. 1. 20:32
  • 프로필 수정 테스트
    • 인증된 사용자만 접근할 수 있는 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 에서 삭제해주어야 한다.