1. Issue Description
WebMvcTest로 MemberController의 로그인 메서드에 대한 테스트 코드를 작성하는 중 403 코드(Forbidden)가 발생하는 오류가 발생하였다.
2. 원인 추론
1️⃣ 첫 번째 의문
forbidden? forbidden은 보통 권한이 없을 때 볼 수 있는 에러코드인데, 나는 로그인 메서드 엔드포인트를 SecurityConfig에서 permitAll() 설정을 해준 상태였다. 왜 forbidden 코드가 떴을까?
WebMvcTest Spring Security 403으로 검색
CSRF 토큰 문제 때문이라고 한다. 실제로 테스트 실패 로그를 보니 세션 쪽에 CSRF 토큰이 있었다.
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/members/login
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"58"]
Body = {"email":"test-user@email.com","password":"test-password"}
Session Attrs = {org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@499c4d61, SPRING_SECURITY_CONTEXT=SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=Mock for UserDetailsImpl, hashCode: 1731036016, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]]]}
2️⃣ 두 번째 의문
난 SecurityConfig에서 CSRF를 disable() 했는데..? 왜 CSRF 토큰이 들어와 있지?
로그인 엔드포인트 permitAll()도 안 먹혀...
CSRF disable()도 안 먹혀...
SecurityConfig가 적용이 안 된다..
이게 내 결론이다(개그욕심)
좀 더 찾다가 https://seongonion.tistory.com/149 글을 보게 되었다.
Spring Boot 2의 Spring Security에서는 SecurityConfig 클래스를 WebSecurityConfigurerAdapter를 상속해서 구현했다. 이렇게 WebSecurityConfigurerAdapter를 상속한 클래스들은 @WebMvcTest 컴포넌트 스캔 대상이 된다.
그런데 Spring Boot 3의 Spring Security에서는 SecurityConfig 클래스를 @Configuration 을 통해 빈으로 등록한다. 따라서 @WebMvcTest의 컴포넌트 스캔 대상이 되지 못한다.
그래서 테스트 코드를 돌렸을 때 내 SecurityConfig 설정이 적용이 하나도 되지 않고 있었고, Spring Security의 기본 SecurityConfig 설정이 적용이 되고 있었던 것이다.
3. 해결
해결할 수 있는 방법은 두 가지가 있다.
- Spring Security의 기본 SecurityConfig 설정으로 유지하면서 테스트를 진행하는 방법
- 이렇게 하는 경우 매 요청마다 CSRF 토큰을 넣어준다.
- 내 SecurityConfig 설정으로 바꿔서 테스트를 진행하는 방법
1번으로 해결하는 경우
다음 코드와 같이 csrf()를 추가해주면 된다. 하지만, 현재 내 SecurityConfig에서는 CSRF 토큰을 사용하지 않을 것이므로 테스트 목적에 어긋난다.
MvcResult result = mvc.perform(post("/foo").with(csrf()))
.andExpect(status().isOk())
.andReturn();
2번으로 해결하는 경우
내 SecurityConfig 설정으로 바꿔주려면 @Import 어노테이션을 이용하여 설정 파일을 가져오면 된다. 이 때, 내 SecurityConfig 클래스에 필요한 의존성도 같이 추가시켜줘야 한다. 나의 경우는 토큰과 관련된 JwtUtil, UserDetailsServiceImpl 의존성이 필요하여 @MockBean으로 의존성을 추가해주었다.
@Import(SecurityConfig.class)
@WebMvcTest(value = MemberController.class)
class MemberControllerTest {
// SecurityConfig 의존성 관리
@MockBean
private JwtUtil jwtUtil;
@MockBean
private UserDetailsServiceImpl userDetailsService;
...
}
4. 결과
해결!
+) 추가 작성
@WithMockUser 어노테이션을 써서 인증된 사용자로써 요청을 보낼 수도 있다. 하지만 기본적으로 제공되는 UserDetails에서 제공되는 username, password, roles, authorities 필드만 사용할 수 있어서 내 경우 UserDetails는 email과 password를 쓸 수 있게끔 커스텀되어 있으므로 내 테스트의 경우와는 맞지 않는다.
아래는 @WithMockUser 를 사용하는 방법이다.
@WebMvcTest(YourController.class) // 테스트할 컨트롤러를 지정
public class YourControllerTest {
@Autowired
private MockMvc mockMvc;
// username과 roles를 지정한 테스트
@Test
@WithMockUser(username = "testUser", roles = {"USER"})
public void testUserRoleAccess() throws Exception {
mockMvc.perform(get("/user-endpoint"))
.andExpect(status().isOk()); // "USER" 권한으로 접근이 가능한 엔드포인트를 테스트
}
// "ADMIN" 역할로 접근이 제한된 경우
@Test
@WithMockUser(username = "testUser", roles = {"USER"})
public void testAdminAccessDenied() throws Exception {
mockMvc.perform(get("/admin-endpoint"))
.andExpect(status().isForbidden()); // "ADMIN" 권한이 없으면 접근 불가
}
}
'STUDY > Trouble Shooting' 카테고리의 다른 글
CORS란? Spring Security에 따른 CORS 설정 (1) | 2024.09.16 |
---|---|
Docker PostgreSQL에서 정렬이 이상하게 되는 문제 (2) | 2024.09.02 |
Timestamped ZonedDateTime 오류 (0) | 2024.05.22 |
Redis Docker Container와 Spring Boot 연결이 안 되는 오류 (0) | 2024.05.22 |
만료된 토큰에서 토큰에 저장된 정보를 가져올 수 없는 오류 (0) | 2024.05.22 |