티스토리 뷰

이번 글에서는 Spring Boot 환경에서 CORS 이슈를 경험하고 해결한 과정을 정리했다. 혹시 CORS를 모르거나 관련된 배경지식에 대해 궁금하다면 아래 포스팅을 참고하면 좋을 것 같다.

 

SOP와 CORS

최근 프로젝트를 진행하면서 CORS 이슈를 경험했다. 과거에도 여러 번 겪었던 문제이지만 계속 미뤄오다가 이번 기회에 공부도 할 겸 자료를 정리해보았다. 이 글에서는 SOP와 CORS, 그리고 CORS의

bool-flower.tistory.com

기본적으로 Access-Control-Allow-Origin 헤더 값을 통해 CORS 설정을 해줄 수 있다. Spring Boot 환경에서는 일반적으로 Filter를 구현해서 헤더 값을 설정한다.

  • CORSFilter를 직접 구현하여 헤더 값 설정
  • CorsConfigurationSource 빈을 Spring Security의 FilterChain에 등록

처음엔 첫 번째 방법으로 했다가, 이후 두 번째 방법으로 변경했다. 첫 번째 방법 먼저 알아보자.

CORSFilter를 직접 구현하여 헤더 값 설정


처음 CORS를 겪었을 때는 어딘가 떠돌아다니는 소스를 가져다가 해결했는데, 그게 알고 보니 이 방법이었다. CORSFilter 클래스를 만들어서 CORS 헤더 설정을 직접 하는 것이다.

@Component
public class CORSFilter implements Filter {


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me, accesss-token, Set-Cookie");
        chain.doFilter(req, res);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }

}

doFilter 내부에서 response.setHeader를 통해 Origin, Methods 등 CORS 정책을 위한 헤더를 설정하고 있다. 이렇게 하고 나니 더 이상 CORS 정책 위반 메시지가 뜨지 않아 잘 해결된 것 같았다. 

response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));

그러나 위와 같이 설정하면 요청을 하는 모든 클라이언트에 대한 접근을 허용한다. 이럴거면 CORS 설정을 하는 이유가 없다. 

response.setHeader("Access-Control-Allow-Origin", "https://www.naver.com");

위처럼 리소스 공유를 허용할 출처를 직접 명시해주어야 해당 출처에만 리소스를 공유한다. 나같은 경우에는 프론트 서버와 통신을 해야하니 프론트 서버의 IP 주소를 명시했다.

또 한가지 개선할 점은 위처럼 Filter를 직접 만들지 않고 Spring Security를 이용해 구성하는 것이었다. Spring Seucrity는 CORS 설정과 해당 필터를 구성하는 기능을 제공하기에 굳이 새로운 클래스를 구현하지 않아도 됐었다. 게다가 프로젝트에는 이미 SecurityConfig라는 이름으로 설정 클래스가 존재했다. (어떻게 쓰는 건지도 잘 모른 채로 일단 당겨와서 최소한의 설정만을 해둔 상태였다.) 또한, 인증이나 보안 관련한 문제는 잘 만들어진 기존의 도구를 안쓸 이유가 없다고 생각했다. 

Spring Security를 이용해 FilterChain에 등록


기존의 CORSFilter 클래스를 지우고 SecurityConfig 클래스에 cors 필터 설정을 적용했다.

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
    
    protected void configure(HttpSecurity http) throws Exception {

        http
                .cors().configurationSource(corsConfigurationSource())
            .and()
                .csrf().disable()
                .headers().frameOptions().disable()
            .and()
                .authorizeRequests().antMatchers()
                .authenticated().anyRequest()
                .permitAll();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {

        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000","http://localhost:3001", frontHost));
        configuration.setAllowedHeaders(Arrays.asList("Content-Type", "Accept", "X-Requested-With", "remember-me", "accesss-token", "Set-Cookie"));
        configuration.setAllowedMethods(Arrays.asList("DELETE","GET","HEAD","OPTIONS","PATCH","POST","PUT"));
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

corsConfigurationSource()보면 이전의 필터 클래스와 비슷하게 헤더를 설정하는 코드가 존재한다. 해당 설정을 corsConfigurationSource 타입의 빈으로 생성하는데, 이를 configurationSource()의 파라미터로 전달해 해당 설정을 적용한다.

프로젝트 실행 후 요청을 보내서 콘솔을 확인해보면 위 사진과 같이 CorsFilter가 Security filter chain으로 구성된 것을 확인할 수 있다. 

참조


https://developer.mozilla.org/ko/docs/Web/Security/Same-origin_policy

https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F

https://velog.io/@chullll/Spring-Security-CORS

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday