본문 바로가기

트러블 슈팅

[Spring] Spring Security + Junit5

환경: Spring Boot version '2.7.5.' + spring webflux + spring security 

 

상황

Security 설정을 다음과 같이 작성하였다.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
                .csrf().disable()
                .authorizeExchange(
                        authorize ->
                                authorize.pathMatchers("/api/v1/members/join").permitAll()
                                        .anyExchange().authenticated()
                )
                .formLogin().disable()
                .httpBasic().disable();


        return http.build();
    }
}

 

이후, 회원 가입 API를 생성하고 테스트를 진행하기 위해 다음과 같은 코드를 작성하였다.

@WebFluxTest(controllers = MemberController.class)
class MemberControllerTest {

    WebTestClient webClient;
    
    @BeforeEach
    void setWebClient() {
        webClient = WebTestClient
                .bindToController(MemberController.class)
                .build();
    }

    @Test
    void 회원가입() {
        String username = "username";
        String password = "password";

        webClient
                .post()
                .uri("/api/v1/members/join")
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(MemberJoinRequest.of(username, password)))
                .exchange()
                .expectStatus().isOk();

    }

}

Security 설정에서 csrf를 disable 했기 때문에, 테스트 코드를 실행하면 200 응답이 올 것으로 예상했는데, 실제로는 403 FORBIDDEN 응답이 왔다.

 

이에, 테스트 코드에 직접 csrf 토큰을 생성해서 요청하도록 코드를 다음과 같이 수정하였다.

@WebFluxTest(controllers = MemberController.class)
class MemberControllerTest {

    WebTestClient webClient;

    @BeforeEach
    void setWebClient() {
        webClient = WebTestClient
                .bindToController(MemberController.class)
                .build()
                .mutateWith(csrf());
    }
    
    @Test
    void 회원가입() {
        String username = "username";
        String password = "password";

        webClient
                .post()
                .uri("/api/v1/members/join")
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(MemberJoinRequest.of(username, password)))
                .exchange()
                .expectStatus().isOk();

    }

}

 

이번에는 401 UNAUTHORIZED 응답이 왔다. 

분명 Security 설정에서는 테스트 URI에 대한 permitAll() 설정을 하였는데 401 에러가 발생했던 것이다. 

 

원인

위와 같은 상황에서 에러가 발생한 이유에 대해서 생각을 했고, 내가 작성한 Security 설정이 정상적으로 작동하지 않고 있다는 결론을 내렸다.

 

내 생각에 대한 확인 결과 기본적으로 @WebMvcTest 나 @WebFluxTest에서 spring-security-test가 클래스 경로에 있으면 스프링 보안을 자동으로 구성하기 때문에 내 설정이 적용 되는 것이 아닌 자동으로 구성된 설정이 적용되는 것이였다.

 

 

해결

해당 문제를 해결하기 위해서 Mock 문서를 확인한 결과 @ContextConfiguration 을 사용하여 내가 작성한 Configuration을 등록하면 내가 설정한 Security 옵션이 적용된다는 것이다.

 

이에 테스트 코드를 다음과 같이 수정하였다.

@WebFluxTest(controllers = MemberController.class)
@ContextConfiguration(classes = {SecurityConfig.class})
class MemberControllerTest {

    WebTestClient webClient;

    @BeforeEach
    void setWebClient() {
        webClient = WebTestClient
                .bindToController(MemberController.class)
                .build()
    }

    @Test
    void 회원가입() {
        String username = "username";
        String password = "password";

        webClient
                .post()
                .uri("/api/v1/members/join")
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(MemberJoinRequest.of(username, password)))
                .exchange()
                .expectStatus().isOk();

    }

}

 

 

위와 같이 테스트가 성공적으로 완료된 것을 확인할 수 있다.