[잔디 일기] Spring Boot에서 CORS 오류 해결과 클래스 관심사 분리: JwtAuthFilter와 WebMvcConfig 활용하기

CORS(Cross-Origin-Resource-Sharing)란?

서로 다른 출처(Origin)에서 리소스를 공유하는 것을 말합니다.

 

URL의 구조

URL은 다음과 같은 6가지 요소로 구성됩니다.

https://www.domain.com:8080/member?query=name&page=1#firsthttps:// www.domain.com :8080 /member ?query=name&page=1 #first
  • Protocol: https://
  • Host: www.domain.com
  • Port: :8080
  • Path: /member
  • Query string: ?query=name&page=1
  • Fragment: #first

여기서 Protocol, Host, Port 부분이 Origin(출처)입니다.

 

CORS 설정이 되어 있지 않으면, 서버와 다른 Origin을 가진 곳에서 요청을 보낼 때 CORS 에러가 발생합니다. 예를 들어, 이 서비스의 프론트엔드는 3000번 포트를 사용하고 서버는 8080 포트를 사용하므로, 포트 차이로 인해 CORS 에러가 발생할 수 있습니다.

 

해결 방법

config 폴더 내에 WebMvcConfig 클래스를 생성하여 addCorsMappings 메서드를 오버라이딩함으로써 CORS를 허용할 수 있습니다.

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("<http://localhost:3000>")
                .allowedMethods("OPTIONS", "GET", "POST", "PUT", "DELETE", "PATCH")
                .allowedHeaders("Authorization", "Content-Type")
                .allowCredentials(true);
    }
}

 

이 방법은 localhost에 대한 접근만 허용하기 때문에, 배포된 URL을 추가해야 합니다.

 

하지만 배포 URL 추가를 시도하던 중, 로그인 기능을 맡은 다른 분이 JwtAuthFilter 클래스에서 CORS 설정을 구현해 둔 것을 발견했습니다. 이 클래스는 OncePerRequestFilter를 상속한 JwtAuthFilter였고, 다음과 같은 방식으로 사용되고 있었습니다.

 

response.setHeader("Access-Control-Allow-Origin", "<http://localhost:3000>");

 

이 방식으로는 여러 도메인을 추가할 수 없어, 아래와 같이 시도해보았습니다.

 

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    String origin = request.getHeader("Origin");

    // 허용할 origin 목록
    List allowedOrigins = Arrays.asList("", "<https://grassdiary.site>");

    if (allowedOrigins.contains(origin)) {
        response.setHeader("Access-Control-Allow-Origin", origin);
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        response.setHeader("Access-Control-Allow-Credentials", "true");
    }

    // ...
}

 

그러나 이 방법도 효과가 없어 결국 다시 WebMvcConfig 클래스로 돌아와 WebMvcConfigurer를 통해 CORS 설정을 처리하기로 했습니다.

 

@Configuration
@EnableWebMvc
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

		// ...

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("", "<https://grassdiary.site>")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedHeaders("*")
            .allowCredentials(true);
    }
    
    // ...
}

 

WebMvcConfigurer는 Spring MVC의 Java 기반 설정을 커스터마이징할 수 있는 인터페이스로,

전역 CORS 요청 처리를 설정하는 메서드를 제공합니다.

 

/**
 * {@code @EnableWebMvc}로 Spring MVC를 활성화한 Java 기반 설정을
 * 커스터마이징할 수 있는 콜백 메서드들을 정의합니다.
 *
 * <p>{@code @EnableWebMvc}가 있는 설정 클래스는 이 인터페이스를 구현하여
 * 기본 설정을 커스터마이징할 기회를 가집니다.
 *
 * @author Rossen Stoyanchev
 * @since 3.1
 */
public interface WebMvcConfigurer {

		// ... 
		
    /**
     * 전역 CORS 요청 처리를 설정합니다. 설정된 CORS 매핑은 주석이 달린 컨트롤러,
     * 기능적 엔드포인트, 정적 리소스에 적용됩니다.
     * <p>컨트롤러 메서드의 주석에서 세밀한 CORS 설정을 추가로 선언할 수 있습니다.
     * 여기서 선언된 전역 CORS 설정은 컨트롤러 메서드의 로컬 CORS 설정과
     * {@link org.springframework.web.cors.CorsConfiguration#combine(CorsConfiguration)}
     * 방식으로 결합됩니다.
     * @since 4.2
     * @see CorsRegistry
     */
    default void addCorsMappings(CorsRegistry registry) {
    }
    
    // ...
}

 

또한 이 인터페이스에는 전역 CORS 요청 처리를 설정하기 위한 메서드가 따로 존재하는데, 이를 통해 CORS 설정은 WebMvcConfig에서 처리하는 것이 구조상 더 적합하다고 느껴졌습니다.

 

결론적으로 해당 작업이 이뤄지면서 JwtAuthFilter 클래스와 WebMvcConfig 클래스의 관심사가 분리되었습니다.

  • JwtAuthFilter 클래스에서 CORS를 담당하던 것을 WebMvcConfig 클래스에서 CORS를 담당하도록 수정하였습니다.
  • JwtAuthFilter 클래스에서는 인증만 담당하도록 하였습니다.

이번 경험을 통해 코드 리뷰의 중요성을 다시 한번 깨달았습니다.

아무리 바쁘더라도 다른 사람의 코드를 꼼꼼히 리뷰하는 것이 중요하며,

구현 중인 클래스가 본래 어떤 역할을 하기 위해 만들어졌는지 정확히 이해하고 사용하는 것이 필요하다는 생각이 들었습니다.

 

연관된 PR들