728x90
반응형
SMALL
테스트 코드를 작성할 때 Spring Security를 적용한 애플리케이션은 어떻게 인증/인가를 해야하는지 알아보자.
다음 테스트 코드를 보자.
package cwchoiit.springsecurity.domain.note.controller;
import cwchoiit.springsecurity.domain.user.entity.User;
import cwchoiit.springsecurity.domain.user.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.TestExecutionEvent;
import org.springframework.security.test.context.support.WithUserDetails;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import static cwchoiit.springsecurity.domain.user.entity.User.Role.ROLE_USER;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@Transactional
@ActiveProfiles("test")
class NoteControllerTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.alwaysDo(print())
.build();
userRepository.save(new User("user", "user", ROLE_USER));
}
@Test
void getNotes_not_authentication() throws Exception {
mockMvc.perform(get("/note"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("**/login"));
}
@Test
@WithUserDetails(
value = "user",
userDetailsServiceBeanName = "userServiceImpl", // UserDetailsService 구현한 구현체의 빈 이름
setupBefore = TestExecutionEvent.TEST_EXECUTION //언제 유저가 세팅되는지를 지정 (TEST_EXECUTION -> 이 테스트가 실행되기 바로 직전에 유저를 세팅)
)
void getNotes_authenticated() throws Exception {
mockMvc.perform(get("/note"))
.andExpect(status().isOk());
}
}
- 우선 당연히 @SpringBootTest 애노테이션을 붙여서, 스프링 애플리케이션으로 테스트를 실행해야 한다. 스프링 시큐리티가 결국 스프링 위에 있는 녀석이니까.
- 그리고 필요한게 MockMvc이다. 요청을 날려봐야 하니까.
그래서, 테스트가 실행되기 전에 사전 작업이 필요한데 그 부분이 아래 부분이다.
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(springSecurity())
.alwaysDo(print())
.build();
userRepository.save(new User("user", "user", ROLE_USER));
}
- @BeforeEach 애노테이션으로 각 테스트가 실행하기 전에 실행되게 한다.
- springSecurity()를 적용해서 스프링 시큐리티 애플리케이션의 테스트임을 지정한다.
- 유저를 Mocking해서 요청 시 인증/인가에 적용하려면 일단 유저가 필요하다. 그래서 `ROLE_USER`권한의 유저를 하나 만든다.
다음은 인증이 되지 않은 요청이 들어왔을 때 테스트 코드이다.
@Test
void getNotes_not_authentication() throws Exception {
mockMvc.perform(get("/note"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("**/login"));
}
- 어떠한 인증도 하지 않은 요청이 들어오면 로그인 페이지로 리다이렉트 되는지를 확인하는 테스트이다.
다음은 인증을 특정 유저로 했을 때 정상적으로 동작하는지를 확인하는 테스트이다. 이 테스트가 중요하다.
@Test
@WithUserDetails(
value = "user",
userDetailsServiceBeanName = "userServiceImpl", // UserDetailsService 구현한 구현체의 빈 이름
setupBefore = TestExecutionEvent.TEST_EXECUTION //언제 유저가 세팅되는지를 지정 (TEST_EXECUTION -> 이 테스트가 실행되기 바로 직전에 유저를 세팅)
)
void getNotes_authenticated() throws Exception {
mockMvc.perform(get("/note"))
.andExpect(status().isOk());
}
- 여기서, @WithUserDetails() 애노테이션으로, UserDetails 타입의 유저를 mockMvc로 요청할 때 사용한다.
- 그러니까, UserDetailsService를 구현한 구현체의 빈 이름을 알려주면, 해당 빈을 찾아서 그 빈이 구현한 loadUserByUsername()을 사용한다. 그때 유저이름을 value값으로 지정한 `user`로 사용한다.
- 쉽게 말해, `/note`로 요청을 날릴 때 SecurityContext안에 Authentication으로 이 유저를 집어넣는다는 의미가 된다. 그래서 실제로 알려준 빈을 통해 loadUserByUsername('user')를 호출해서 찾아진 유저를 SecurityContext안에 넣는다.
참고로, @WithMockUser, @WithAnonymousUser라는 애노테이션도 있는데, @WithMockUser는 반환하는 타입이 org.springframework.security.core.userdetails.User라서 만약, 서비스 내에서 UserDetails를 구현한 본인만의 User를 사용한다면 이 애노테이션을 사용할 순 없다. 그리고 @WithAnonymousUser는 말 그대로 Authentication 정보로 `anonymousUser`가 들어간 상태로 테스트가 진행된다.
또한, @WithSecurityContext 애노테이션도 있는데, 이 애노테이션은 Authentication을 가짜로 만드는게 아니라, 아예 진짜 SecurityContext를 만드는 애노테이션이다.
728x90
반응형
LIST
'Spring Security' 카테고리의 다른 글
Spring Security Part.4 - 커스텀 필터 등록 (0) | 2024.10.06 |
---|---|
Spring Security Part.2 - Spring Security Configuration (5) | 2024.10.06 |
Spring Security Part.1 - 내부 구조 파악, Spring Security가 사용하는 여러 필터들 (0) | 2024.10.03 |