티스토리 뷰

SW 개발/웹

AJAX 요청에 대한 CORS 허용하기

지단로보트 2021. 4. 8. 14:48

Origin이란?

  • Origin은 아래와 같이 protocol, host, port 3개 부분으로 구성된다.
http://www.jsonobject.com:8080/
# protocol: http
# host: www.jsonobject.com
# port: 8080
  • 2개의 Origin을 비교시 3개 부분 중 단 1개만 일치하지 않아도 서로 다른 Origin이 된다. 즉, http, https의 차이, 80, 8080의 차이만 나도 서로 다른 Origin이 된다.

브라우저에서 현재 페이지의 Origin 알아내기

브라우저에서 현재 페이지의 정확한 Origin을 알고자 한다면 JavaScript에서 아래 명령을 실행한다.

# IE를 제외한 모든 브라우저
window.location.origin

# IE
window.location.protocol + '//' + window.location.hostname + ':' + window.location.port
  • 브라우저에 따라 현재 페이지의 Origin을 획득하는 방법은 위와 같이 2가지로 구분된다.

CORS

  • 기본적으로 브라우저는 현재 페이지에서 Origin이 다른 URL로의 AJAX 요청을 원천적으로 차단한다. 사실 브라우저는 Origin이 다른 URL에 대한 AJAX 요청 후 응답을 이미 받은 상태로 응답 헤더의 CORS 정보를 확인하여 차단하는 것이다.
  • CORS란 무엇일까? CORS(Cross Origin Resource Sharing)는 서로 다른 Origin 간의 AJAX 요청-응답을 서버에서 제어할 수 있도록 만들어진 W3C의 표준이다. 요청을 받는 서버 측은 CORS 정책을 명시하여 클라이언트에 따라 AJAX 요청을 허용할지 말지를 결정할 수 있다.

  • 최근 유행하는 토큰 인증 기반의 웹 애플리케이션의 구조는 페이지(https://app.jsonobject.com) 상에서 API 서버(https://api.jsonobject.com)로 바로 AJAX 요청을 하는 구조로 진행되기에 AJAX 요청을 받는 서버 측에서 클라이언트의 Origin을 허용하는 작업이 필요하다. 서버에서 응답 헤더에 아래 정보를 추가하면 된다.
# AJAX 요청을 허용할 클라이언트의 Origin을 명시한다.
Access-Control-Allow-Origin: https://app.jsonobject.com

# Origin의 구분 없이 모든 클라이언트로부터 AJAX 요청을 허용한다.
Access-Control-Allow-Origin: *

CORS Preflight Request

  • AJAX 요청이 발생할 때 Origin이 다를 경우, 실제 요청에 앞서 브라우저에서 자동으로 CORS Preflight Request를 전송하여 응답으로 대상 서버가 CORS를 허용하는지 확인한다. 이 때, 대상 서버는 OPTIONS 요청에 대해 인증 등의 처리에서 예외되도록 처리해야 한다. [관련 링크 1] [관련 링크 2]

NGINX 리버스 프록시에서의 CORS 허용 설정

  • NGINX를 리버스 프록시로 사용할 경우, 아래와 같이 환경 설정 파일을 수정하여 원하는 오리진에 대해 CORS를 허용하도록 설정할 수 있다. [관련 링크]
server {
  listen 8080;

  location / {
    proxy_pass https://api.foobar.com;
    proxy_hide_header Access-Control-Allow-Origin;
    proxy_hide_header Access-Control-Allow-Methods;
    add_header Access-Control-Allow-Origin * always;
    add_header Access-Control-Allow-Methods 'HEAD, OPTIONS, GET, POST, PUT, PATCH, DELETE';
    add_header Access-Control-Allow-Headers *;
    if ($request_method = OPTIONS) {
      return 200;
    }
  }
}
  • proxy_hide_header 옵션은 원본 서버의 응답에 포함된 Access-Control-Allow-Origin 응답 헤더를 제거하는 역할을 한다. 이 설정을 하지 않을 경우, 아래에서 새로운 응답 헤더를 설정해도 값이 교체되지 않고 누적되기 때문에 중요한 설정이라고 할 수 있다.
  • add_header 옵션을 통해 CORS를 허용할 오리진을 새롭게 명시할 수 있다. *를 명시하면 모든 원격지에 대한 AJAX 요청을 허용한다는 의미이다.
  • always 옵션은 모든 조건의 응답에 대해 이 응답 헤더를 포함한다는 의미이다. 이 설정을 하지 않을 경우, 2XX에 해당하는 시작하는 성공 응답에만 응답 헤더가 포함된다.
  • 요청 메써드가 OPTIONS일 경우 무조건 200 OK을 하도록 설정했다. 이유는 CORS Preflight RequestOPTIONS 요청이 200 OK을 요구하기 때문이다. (IE11 등의 구형 브라우저에서는 204 No Content를 요구하기도 한다. 표준이 정확히 정립되지 않은 탓에 차이가 있다.) 이 설정을 하지 않을 경우 아래 오류가 발생하는 것을 확인했다.
Access to XMLHttpRequest at '{request-url}' from origin '{request-origin}' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

Spring Boot에서의 CORS 허용 설정

  • Spring Boot 프로젝트에서는 아래와 같이 빈 설정을 추가하여 전역 CORS 허용을 설정할 수 있다.
@Configuration
class CorsConfig {

    @Bean
    fun corsFilter(): CorsFilter {

        val config = CorsConfiguration().apply {
            allowCredentials = true
            allowedOrigins = listOf("*")
        }
        val source = UrlBasedCorsConfigurationSource().apply {
            registerCorsConfiguration("/**", config)
        }

        return CorsFilter(source)
    }
}

CORS 작동 테스트하기

  • 아래와 같이 간단한 코드로 브라우저에서 CORS 작동 여부를 빠르게 확인해볼 수 있다.
<html>
   <head>
      <title>CORS Test</title>
   </head>
   <body>
      <script>
         var http = new XMLHttpRequest()
         http.open("{method}", "{url}", true)
         http.setRequestHeader("{header-name}", "{header-value}")
         http.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
         http.send(JSON.stringify({ "{parameter-name}": "{parameter-value}" ))
      </script>
   </body>
</html>

참고 글

댓글
댓글쓰기 폼