기존 controller layer test 코드가 pr merge 전 403 에러가 발생하는 문제가 발생했습니다.
@DisplayName("유저를 신고 할 수 있다.")
@Test
void reportUserTest() throws Exception {
// given
UserReportRequest request = new UserReportRequest(1L, 2L, UserReportType.OTHER, "신고 내용 쏼라 쏼라 쏼라 ");
UserReportResponse response = of(SUCCESS, 1L, 2L, "신고한 유저 이름");
// when
when(userReportService.userReport(request)).thenReturn(response);
// then
mockMvc.perform(post("/api/v1/reports/user")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value("true"))
.andExpect(jsonPath("$.code").value("200"))
.andExpect(jsonPath("$.data.message").value(SUCCESS.getMessage()))
.andExpect(jsonPath("$.data.reportId").value(response.getReportId()))
.andExpect(jsonPath("$.data.reportUserId").value(response.getReportUserId()))
.andExpect(jsonPath("$.data.reportUserName").value(response.getReportUserName()))
.andDo(print());
}
{org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@63a72cc6, SPRING_SECURITY_CONTEXT=SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER]]]}
하지만 매우 친철한 spring CSRF 토큰이 없어서 발생하는 문제라는 것을 설명해주고 있습니다.
원인 유추는 생각보다 간단했습니다.
현재 작업 중인 pr/38에 main을 병합하는 과정하고 추가 된 코드가 이슈가 있을거라 판단이 되었습니다.
다른 브랜치에서 security config 관련 사전 설정이 추가되었는데.
해당 부분의 .csrf(AbstractHttpConfigurer::disable)
가 문제로 파악되었습니다.
Cross-Site Request Forgery
해결책은 간단합니다:
// then
mockMvc.perform(post("/api/v1/reports/user")
.contentType(MediaType.APPLICATION_JSON)
.with(csrf())
.content(mapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value("true"))
.andExpect(jsonPath("$.code").value("200"))
.andExpect(jsonPath("$.data.message").value(SUCCESS.getMessage()))
.andExpect(jsonPath("$.data.reportId").value(response.getReportId()))
.andExpect(jsonPath("$.data.reportUserId").value(response.getReportUserId()))
.andExpect(jsonPath("$.data.reportUserName").value(response.getReportUserName()))
.andDo(print());
mockMvc.perform()
부분에 .with(csrf()))
를 추가하면 된다.
테스트에서 .with(csrf())
를 사용하면 CSRF 토큰이 자동으로 테스트 요청에 포함됩니다.
이는 실제 웹 애플리케이션에서 폼 제출 시 CSRF 토큰이 포함되는 것을 모방합니다.
다만 이런 식으로 하면 논리적으로 좀 이상함을 느낄수도 있습니다.
분명 security에는 csrf을 disable 하였지만. 테스트 시에는 허용하되 통과 하도록 한 것이다보니
좀더 직관적인 방식으로 적용하기로 했습니다.
@WebMvcTest(ReportCommandController.class)
@WithMockUser
@Import(SecurityConfig.class)
class ReportCommandControllerTest {
@Import(SecurityConfig.class)
이 방식은 후속적인 필터나 검증 요소로 인해 충돌이 발생할 가능성이 있으나,
그런 경우 이 글을 통해 추가적인 정보를 제공하는 것이 좋을것 같습니다