CORS란 무엇일까?

박병욱·2025년 4월 30일

TIL

목록 보기
8/11
post-thumbnail

🚫 CORS란?

CORS(Cross-Origin Resource Sharing)는 출처가 다른 곳의 리소스를 요청할 때 접근 권한을 부여하는 메커니즘이다. 프론트엔드 프로젝트를 하다 보면 자주 마주치는, 보기만 해도 화가 나는 오류다.

❓ 왜 이런 오류가 발생할까?

결론만 말하자면, 브라우저가 내 서버가 아닌 다른 서버의 리소스를 요청했기 때문이다. 브라우저는 기본적으로 다른 서버를 신뢰하지 않기 때문에 출처(origin)가 다른 서버에 요청하거나 응답을 받는 걸 차단한다. 그 이유는 간단하다. 쿠키나 토큰과 같이 민감한 사용자 정보가 저장되어 있는 브라우저 환경을 보호하기 위해서이다.

대표적인 보안 이슈는 아래와 같다.

그래서 브라우저는 기본적으로 “Same-Origin Policy(SOP, 동일 출처 정책)” 을 따른다. 출처가 다르면 자바스크립트에서 데이터를 가져올 수 없도록 막는 것이 SOP의 핵심이다. 즉, CORS 오류는 SOP 정책을 위반했기 때문에 발생한다.

 

🌐 출처란 무엇인가?

CORS의 C는 “교차(Cross)”를 의미하고, O는 “출처(Origin)” 를 의미한다. 출처는 단순한 도메인이 아니라, 아래 세 요소를 모두 포함한 것을 말한다.

구성 요소예시
프로토콜http, https
도메인www.myshop.com
포트:3000, :8080

  1. 브라우저에서 https://myshop.com의 출처를 가진 웹 애플리케이션에서 https://othershop.com 출처의 리소스를 요청한다.
  2. https://othershop.com 출처에서는 https://myshop.com 출처의 리소스 요청을 거부한다.

 

이럴 때 브라우저는 보안 상의 이유로 https://othershop.com의 리소스를 https://myshop.com에서 요청할 수 없도록 차단하고 CORS 에러를 발생시키는 것이다. “CORS를 한 마디로 정의하자면 서로 다른 서버끼리 리소스를 공유하기 위한 정책“ 인 것이다. 리소스 요청, 응답을 허용할지 결정하는 브라우저의 검증과 허락, 그를 위한 HTTP 헤더 사용 등을 포함한다.

예전에는 프론트엔드와 백엔드를 따로 구성하지 않고 한 번에 구성해서 모든 처리가 같은 도메인 안에서 가능했다. 그래서 다른 출처로 요청을 보내는 게 당연히 의심스러운 행위로 보일 수밖에 없다. 그런데 시간이 지나 클라이언트에서 API를 직접 호출하는 방식이 당연해지기 시작했다. 그런데 생각해보면, 보통 클라이언트와 API는 다른 도메인에 있는 경우가 많다. 그래서 CORS 정책이 생긴 것이다. 출처가 다르더라도 요청과 응답을 주고받을 수 있도록 서버에 리소스 호출이 허용된 출처(Origin)를 명시해 주는 방식으로 말이다.

CORS는 “교차 출처 리소스 공유” 이다. 여기서 출처가 교차한다는 게 무슨 뜻일까? 앞서 살펴봤던 출처를 구성하는 세 요소 중 하나라도 다르면 CORS 에러를 만나게 되는 것이다.

  • 도메인(Hostname): myshop.com
  • 출처(Origin): https://www.myshop.com

즉, “출처가 교차한다” 는 의미는, 리소스를 주고받으려는 “두 출처가 서로 다르다” 는 뜻이다. CORS를 설정한다는 건 출처가 다른 서버 간의 리소스 공유를 허용한다는 의미다. 위에서 SOP가 서로 다른 출처일 때 리소스 요청과 응답을 차단하는 정책이라면, CORS는 반대로 서로 다른 출처라도 리소스 요청, 응답을 허용할 수 있도록 하는 정책이다. 그래서 우리가 만나는 에러는 CORS가 가능하도록 무슨 헤더를 다시 설정하라는 가르침을 준다.

뒤에 나올 해결 방법에서 사용되는 헤더인 Access-Control-Allow-Origin"허용되는 출처에 대한 접근제어" 라는 의미라고 이해할 수 있다.

아래 예시를 통해 어떤 URL이 SOP에 부합하는지 한 번 확인해 보자.

URL접근이 가능한가? (SOP를 준수했는가?)
https://www.myshop.com/example/✅ 프로토콜, 도메인, 포트가 같음
https://myshop.com/example2/✅ 프로토콜, 도메인, 포트가 같음
http://myshop.com/example/❌ 프로토콜과 포트가 다름
http://en.myshop.com/example/❌ 도메인이 다름
http://www.myshop.com/example/❌ 프로토콜이 다름
http://myshop.com:8080/example/❌ 포트가 다름

 

🤔 CORS의 작동 방식

브라우저는 요청을 보낼 때 Origin 헤더를 포함한다. 그리고 서버는 응답에 Access-Control-Allow-Origin헤더를 포함해서 이 요청을 허용할지 판단한다.

🟢 Simple Request

다음 조건을 모두 만족하면 CORS 사전 요청 없이 바로 전송된다:

  • GET, POST, HEAD 메서드
  • 수동 설정된 헤더가 없음
  • Content-Typeapplication/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나

 

🟠 Preflight Request (사전 요청)

다음 중 하나라도 해당되면 브라우저는 본 요청 전에 OPTIONS 메서드로 사전 요청을 보낸다.

  • 메서드가 PUT, DELETE 등 단순하지 않은 경우
  • 커스텀 헤더 사용 (Authorization, X-Custom-Header 등)
  • Content-Type이 특수한 경우

 

Preflight Request는 추가로 Access-Control-Request-Method로 실 요청 메서드와, Access-Control-Request-Headers 헤더에 실 요청의 추가 헤더 목록을 담아서 보내야 한다.

<Preflight 요청 예시>
OPTIONS /some-api HTTP/1.1
Origin: https://myshop.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Custom-Header

<Preflight 응답 예시>
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myshop.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 86400

이에 대한 응답은 대응되는 Access-Control-Allow-Methods와 Access-Control-Headers를 보내야 하고, Preflight Request로 인한 추가 요청을 줄이기 위해 캐시 기간을 Access-Control-Max-Age에 담아서 보내야 한다.

또한 인증된 요청을 사용하는 방식도 있다. 이를 Credential Request 라고 한다.

 

🔐 Credential Request

토큰, 세션, 쿠키 등 인증 정보가 포함된 요청은 Credential Request로 분류된다.

  • 요청 시 credentials: 'include'를 명시해야 한다.
  • 서버는 반드시 아래와 같이 응답해야 한다:
(올바른 방식)
Access-Control-Allow-Origin: https://myshop.com
Access-Control-Allow-Credentials: true

인증 정보(Credentials)를 포함하는 요청에서는 반드시 명시적인 출처를 설정하자.

(잘못된 방식)
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

 

🎉 CORS 에러 해결하기

  1. 서버에서 Access-Control-Allow-Origin 응답 헤더 세팅하기 (권장)

서버에서 Access-Control-Allow-Origin 헤더를 설정해서 요청을 수락할 출처를 명시적으로 지정할 수 있다. 이 헤더를 세팅하면 출처가 다르더라도 https://myshop.com의 리소스 요청을 허용하게 된다.

'Access-Control-Allow-Origin': <origin> | *

위와 같은 식으로, * 로 설정하면 출처에 상관없이 리소스에 접근할 수 있는 와일드카드이기 때문에 보안에 취약해진다. 그래서 'Access-Control-Allow-Origin': https://myshop.com과 같이 직접 허용할 출처를 명시하는 방법이 권장된다.

 

  1. 프록시 서버 사용하기

브라우저가 아닌 중간 서버에서 요청을 보내게 하여 CORS를 우회한다.

  • 클라이언트 → 프록시 서버 → 대상 서버
  • 대상 서버는 프록시 서버를 같은 출처로 인식하기 때문에 CORS 오류가 나지 않음

예를 들어, http://example.com 라는 주소의 웹 애플리케이션이 http://api.example.com라는 리소스에서 데이터를 요청하는 상황을 가정해 보자. 웹 애플리케이션은 직접적으로 리소스에 요청하는 대신, http://example-proxy.com 라는 프록시 서버에 요청을 보낼 수 있다. 그러면 프록시 서버가 http://api.example.com으로 요청을 전달하고, 응답을 다시 웹 애플리케이션에 반환하는 것이다. 이렇게 하면 요청이 http://example-proxy.com 에 보내진 것처럼 보이므로, CORS 에러를 피할 수 있다.


<참고 자료>
결제 창에서 CORS 대응하기
CORS는 왜 이렇게 우릴 힘들게 하는 걸까?
MDN - 교차 출처 리소스 공유 (CORS)

profile
도메인을 이해하는 백엔드 개발자(feat. OOP)

0개의 댓글