스프링 시큐리티를 사용해서 로그인을 구현하고, 인증이 된 사용자에 한해서 사용이 가능하도록 몇몇 페이지의 접근을 제한하였다.
이 제한있는 페이지들을 구현하면서, 테스트 코드를 작성하지 않을 수가 없었는데, 로그인이 된 사용자들을 어떻게 처리해야 할지 고민이었다.
원래대로면, 회원가입 -> 로그인 -> 인증 요구 페이지 순대로 해야 접근이 가능하는데, 앞으로 많은 인증요구 페이지를 작성하면서 이 과정을 일일히 다 쓰는것은 비효율적이라고 생각했다.
그래서 열심히 구글링을 한 결과,
스프링 시큐리티에서는 인증 관련 어노테이션을 제공하였다.
@WithMockUser 와 같은 어노테이션은 jwt를 사용하여, 헤더를 통해 토큰을 주고받는 나에게 적합한 방법이 아니었고,
좀 더 유연하게 원하는 Authentication을 사용하고자 한다면, @WithSecurityContext를 사용하는 것이 좋다고 생각되었다.
먼저 어노테이션을 만들어주고,
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithAccountSecurityContextFactory.class)
public @interface WithAccount {
String value();
}
그다음에, @WithSecurityContext애노테이션에 전달한 WithMockCustomUserSecurityContextFactory클래스를 만든다.
public class WithAccountSecurityContextFactory implements WithSecurityContextFactory<WithAccount> {
@Autowired
MemberService memberService;
@Autowired
PrincipalDetailsService principalDetailsService;
@Override
public SecurityContext createSecurityContext(WithAccount annotation) {
String username = annotation.value();
String email = username + "@aaa.com";
String password = "abcdefgh1234";
String interest = "기타";
Member member = Member.builder()
.email(email)
.username(username)
.password(password)
.interest(interest)
.build();
memberService.postMember(member);
UserDetails userDetails = principalDetailsService.loadUserByUsername(email);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
그리고 이렇게 만든 어노테이션을 테스트 코드에 적용하여, 로그인 과정을 따로 작성해주지 않고, 인증이 요구되는 기능을 테스트해볼 수 있다.
@SpringBootTest
@Transactional
@Rollback(false)
@@ -23,6 +34,65 @@ class MemberTest {
@PersistenceContext
EntityManager em;
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@AfterEach
void afterEach() {
memberRepository.deleteAll();
}
/**
* username : creamyyy
* email : creamyyy@aaa.com
* password : abcdefgh1234
* interest = 기타
*/
@DisplayName("개인 정보 수정 테스트")
@WithAccount("creamyyy")
@Test
void updateMyMember() throws Exception {
//given
Member member = memberRepository.findByEmail("creamyyy@aaa.com").get();
Long id = member.getId();
String beforePassword = member.getPassword();
String changePassword = "aaaaaaa444";
member.changePassword(changePassword);
//when
Member afterMember = memberService.updateMember(member, id);
//then
assertThat(beforePassword).isNotEqualTo(afterMember.getPassword());
System.out.println("==========================================================================");
System.out.println("beforePassword = " + beforePassword);
System.out.println("afterMember.password = " + afterMember.getPassword());
}
}
참고
WithSecurityContextFactory 구현체에서 매번 새로운 계정을 생성해주는 것이기 때문에 @AfterEach 어노테이션을 통해 테스트가 끝날 때마다 계정을 삭제해줘야 한다. 삭제를 해주지 않을 경우 아이디가 중복되어 에러가 발생하였다.