본문 바로가기
프로젝트

GiftFunding) TroubleShooting - Member API 컨트롤러 테스트 중 401 에러

by son_i 2024. 4. 18.
728x90

아직 RestDocs는 만들지 않은 상태로 회원가입 성공 컨트롤러 테스트를 작성하였다. 코드는 아래

@WebMvcTest(value = MemberController.class,
        excludeFilters = {
                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class),
                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = JwtAuthenticationFilter.class)
        }
)
@AutoConfigureRestDocs
class MemberControllerTest {
    @MockBean
    private MemberServiceImpl memberService;

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Autowired
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(webApplicationContext)
                .apply(springSecurity())
                .defaultRequest(post("/**").with(csrf()))
                .defaultRequest(put("/**").with(csrf()))
                .defaultRequest(delete("/**").with(csrf()))
                .build();
    }
    @Test
    @DisplayName("회원가입 성공 테스트")
    void singUpSuccessTest() throws Exception {
        //given
        Signup.Request request =
                Signup.Request.builder()
                        .name("소은")
                        .phone("010-1111-1111")
                        .email("soni@naver.com")
                        .password("qwerty")
                        .address("서울특별시 강남구")
                        .birthDay("2000-01-28")
                        .build();

        given(memberService.signUp(request))
                .willReturn(new Signup.Response(
                        "소은", "님 회원가입이 완료되었습니다."));
        //when
        //then
        mockMvc.perform(post("/member/signup")
//                        .header("Authorization", "Bearer AccessToken")
                        .content(objectMapper.writeValueAsString(request))
                        .contentType(MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("소은"));
    	}
    }

 

그런데 다음과 같은 오류가 나왔다.

대체 왜 ?????

SecurityConfig에서도 이렇게 해당 URL에 대해서는 모든 권한을 허용하도록 해놨다.

                .antMatchers("/member/signup", "/member/signin", "/oauth/**").permitAll()

 

따라서 postman에서 요청할 때도 AccessToken을 넣지 않고 정상적으로 동작한다.

 

로직이 로그인할 때 AccessToken이 발급되므로 회원가입하는 사람은 Token을 가지고 있을 수가 없는 구조이다.

그래서 전에 만들어놨던 @WithMockUser 없이 동작을 해야한다 ! 

 

excludeFilters로 SecurityConfig를 회피하도록도 해놓았는데 왜 안 될까 ?

 

정말 이해할 수가 없군....... 이전 FriendController 테스트 할 때는 이 방식으로 해서 잘 동작했는데 말이다.

 

시도한 방법들

1. excludeFilter 로 SecurityConfig.class와 JwtAuthenticationFilter.class를 거치지 않도록 해주었다.

  -> 해결되지 않음

 

2. @WebMvcTest에서 Spring-security-test 가 클래스 경로에 있으면 스프링 보안을 자동으로 구성하기 때문에 내 설정이 아닌 자동으로 구성된 설정이 적용된다고 한다. 

 -> @ContextConfiguration(classes = {SecurityConfig.class})를 해주어 내가 설정한 Security 옵션을 적용하도록 하였다.

 -> 그래도 해결되지 않음.

 

 

3. Security자동 설정을 막기 위해 excludeAutoConfiguration = SecurityAutoConfiguration.class을 추가해주었다.

 -> 해결 ! - 아예 Controller 테스트 시 SpringSecurity 설정을 제외 하는 방법.

 => 역시 결론은 Spring Security의 자동 설정이 적용되어 그런 것 같다. 이렇게 하면 csrf() 설정을 해주지 않아도 테스트가 잘 통과한다.

@WebMvcTest(controllers = MemberController.class,
        excludeAutoConfiguration = SecurityAutoConfiguration.class,
        excludeFilters = {
                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class),
                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = JwtAuthenticationFilter.class)
        }
)

 

4. 테스트용 예외 설정을 따로 추가하는 방법

아래처럼 테스트 코드 내에 테스트용 설정 클래스를 만들어주었다.

   @TestConfiguration
    static class DefaultConfigWithoutCsrf extends WebSecurityConfigurerAdapter {

//        @Override
//        protected void configure(final HttpSecurity http) throws Exception {
//            super.configure(http);
//            http.csrf().disable();
//        }

        public void configure(WebSecurity web) throws Exception {
            web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
            web.ignoring().antMatchers("/member/signup"); // 테스트용 API의 Security 비활성화
        }
    }

주석처리한 부분은 csrf를 disable() 하는 부분인데 없어도 테스트가 잘 통과한다.

 

3번이나 4번으로 해결하면 된다 ! ~.~

 

 

MemberController에서 회원가입, 로그인은 @WithMockUser라든지 가짜 사용자 객체가 필요없지만

userInfo, update 메소드는 로그인한 사용자를 기준으로 정보를 보여주거나 정보를 수정하기 때문에 필요하다.

@Test
    @DisplayName("본인 정보 조회 성공 테스트")
    void userInfoSuccessTest() throws Exception {
        //given
        Product product1 = new Product(1L, "반지", 10000L, 1);
        Product product2 = new Product(2L, "목걸이", 20000L, 2);

        List fundingProductDtoList = Arrays.asList(
                FundingProductDto.builder()
                        .id(1L)
                        .product(product1)
                        .total(5000L)
                        .build(),
                FundingProductDto.builder()
                        .id(2L)
                        .product(product2)
                        .total(1000L)
                        .build()
        );

        given(memberService.userInfo(any()))
                .willReturn(new UserInfoResponse(
                        "소은",
                        "010-1111-1111",
                        "soni@naver.com",
                        "서울특별시 강남구",
                        LocalDate.of(2000, 01, 28),
                        new ArrayList<>(fundingProductDtoList)
                ));
        //when
        //then
        mockMvc.perform(get("/member/info"))
                .andDo(print())
                .andExpect(status().isOk());
     }

 여기서 값을 given절에 any()로 넣었기 떄문에 어떤 요청이 들어가든지 mocking 해놓은 결과값이 나오게 된다.

@WithMockUser를 해주지 않아도 테스트가 통과한다.

 

어떻게 할지 생각을 해봤다.

 

1. Security 설정을 넣고 로그인이 필요없는 API 테스트에도 @WithMockUser를 붙여주기

위에서 설정해줬던

excludeAutoConfiguration = SecurityAutoConfiguration.class,

를 없애면 csrf() 에러가 나는데

 

일일히 .with(csrf()) 해주기가 귀찮아서 @TestConfiguration 클래스를 만들었다.

@TestConfiguration
    static class DefaultConfigWithoutCsrf extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(final HttpSecurity http) throws Exception {
            super.configure(http);
            http.csrf().disable();
        }
    }

이렇게 할 경우에는 원래 WithMockUser가 필요없던 회원가입, 로그인에도 @WithMockUser를 넣어줘야 함.

 

2. 기존 해놨던 것처럼 Security 설정 무시하고 그냥 @WithMockUser 필요한 API에도 안 붙여주기

뭐 이렇게 해도 security 인증과정을 거치지 않으니 401, 403 에러가 나오지 않고 given절에 any()로 하기 때문에 응답은 Mocking한 그대로 나온다.

 

처음엔 1로 바꾸려고 했는데 생각해보니까 @WithMockUser를 넣어준다고 any()를 안 쓰는 건 아니라 어차피 @WithMockUser에 사용자 정보로 뽑아올 수 있는 건 아니라 ,,, 2번으로 하기로 했다.

 

엥 근데 왜 any()해줬드라 ?!

아 맞다 내가 Mocking 해준 Member 객체와 실제 요청에 들어가는 Member 객체가 달라서 오류가 나서 그랬다.. . . 

이경우 any() 로 해주고 jsonPath에 대한 추가 검증을 하거나 equals & hashcode를 구현해주면 된다고 했는데 ,.,.,

아 any()로 하는 거 정말 정말 맘에 안 든다 

 


참고

https://jiwondev.tistory.com/270

 

spring security 테스트용 설정 방법

공식문서 한글번역 Testing 스프링 시큐리티를 테스트하는 방법을 설명합니다. 공식 문서에 있는 “Testing” 챕터를 한글로 번역한 문서입니다. godekdls.github.io # 단위테스트 @WebMvcTest의 경우 테스트

jiwondev.tistory.com

https://yeongchan1228.tistory.com/88

 

[Spring] Spring Security + Junit5

환경: Spring Boot version '2.7.5.' + spring webflux + spring security 상황 Security 설정을 다음과 같이 작성하였다. @Configuration @EnableWebFluxSecurity public class SecurityConfig { @Bean SecurityWebFilterChain securityWebFilterChain(Serv

yeongchan1228.tistory.com