CORS란 무엇인가?

한장희·2025년 9월 20일

CORS

CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)는 브라우저가 자신이랑 같은 출처(Same Origin)가 아닌 다른 출처로부터 자원(Resource)을 요청하는 것을 허용하도록 서버가 허가해 주는 것이다. 그렇다면 출처(Origin)는 무엇을 기준으로 판단하는가?

출처(Origin)

우리는 아래와 같은 URL주소를 가지고 인터넷 사이트에 접속하게 된다. 이 URL을 분해해서 출처가 무엇인지 파악해 보겠다.

https://www.example.com:5000/post?query=title&page=1#subjet

  • https:// - 프로토콜(Protocol)
    URL에서 요청하는 통신 프로토콜을 나타내고, 프로토콜이란 웹에서 데이터를 어떻게 전송할지에 대한 통신 규약이다.
  • www.example.com - 호스트(Host)
    호스트는 URL서버의 주소를 나타내고 일반적으로 IP주소(예: 192.168.1.1) 대신 사람이 읽을 수 있는도메인 이름을 사용한다.
  • :5000 - 포트(Port)번호
    포트 번호는 서버의 특정 서비스가 연결된 통신 포트를 나타내고, 기본 포트가 아니면 명시해야한다.
  • /post - 경로(Path)
    요청하는 웹 서버 내에서 리소스나 페이지를 식별하는 주소이다.
  • ?query=title&page=1 - 쿼리 문자열(Query String)
    키-값 쌍으로 구성되며, 웹 서버에 추가적인 정보를 전달할 때 사용된다. 여러 쌍은 &로 구분한다.
  • #subjet - 프래그먼트(Fragment)
    페이지 내 특정 위치를 나타낸다. 서버에 요청을 보내지 않고, 클라이언트 측에서 스크롤이나 내비게이션을 처리하기 위한 식별자로 사용된다.

URL은 위에서 분해한 것처럼 다양한 속성들로 구성이 되는데 출처(Origin)는 Protocal, Host, Port를 합친 것을 의미한다.

SOP(Same-Origin Policy,동일 출처 정책)

SOP는 웹 브라우저가 다른 출처에서 오는 요청을 제한하는 보안 매커니즘이다. 다른 출처에서 오는 요청을 허용하는 CORS랑은 반대된다. (여기서 출처를 비교하고 차단하는 작업은 서버가 아니라 브라우저가 한다) 이를 통해 XSS(Cross-Site Scripting, 크로스사이트 스크립팅)과 CSRF(Cross-Site Request Forgery, 크로스사이트 요청 위조)와 같은 보안 취약점을 방지할 수 있다.
하지만 SOP를 따라서 다른 출처에서 오는 요청을 전부차단하면 인터넷이 작동이 되지 않을 것이다.
그래서 우리는 SOP를 우회해서 다른 출처로부터 리소스를 가져올 수 있게 해야한다. 이때 필요한 것이 CORS다. 개발 환경에서는 중간 서버 역할을 하는 프록시 서버를 사용하여서 CORS에러를 우회할 수도 있다. webpack-dev-server나 Create React App에서 프록시 설정을 하면 된다.

CORS

요청 방식에 따른 CORS

  • <img>, <video>, <script>, <link> 태그는 기본적으로 Cross-Origin 정책을 지원한다.
    <link> 태그의 href 속성을 사용해서 다른 출처의 .css 리소스에 접근 가능
    <script> 태그의 src에서 다른 사이트의 .js 리소스에 접근 가능
    <img> 태그의 src 속성을 사용해서 다른 사이트의 .png, .jpg 등의 리소스에 접근 가능
<link rel="stylesheet" href="" />
<script src=""></script>
<img src="" />
  • XMLHttpRequest, Fetch API는 SOP를 따른다.
    다른 출처의 리소스에 대해 ajax 요청 api 호출시.
    웹 폰트 CSS 파일 내 @font-face에서 다른 도메인의 폰트 사용 시.

각 정책에 따라서 같은 도메인 서버에서 똑같은 이미지 파일을 불러와도 <img>태그의 src 속성을 사용할 때는 괜찮은데 자바스크립트로 ajax 요청으로 가져올 때는 CORS에러가 발생 할 수가 있다.

CORS에러

CORS에러는 브러우저의 SOP 정책을 위반하면서 CORS 정책을 따르지 않아서 발생한 에러이다. CORS 에러는 SOP정책을 위반하여도 CORS 정책을 따르면 해결이 된다. 즉, CORS에러를 해결하는 것은 SOP 파트에서 설명한 것처럼 SOP를 우회하는 것이다. 먼저 브라우저의 CORS 동작 과정을 살펴보겠다.

  1. 클라이언트(브라우저)에서 HTTP 요청 헤더에 Origin이라는 필에 출처를 담아서 서버에 요청을 보낸다.
  2. 서버는 응답 헤더에 Access-Control-Allow-Origin이라는 필드에 이미 지정된 허용할 url을 담아 클라이언트로 전달한다.
  3. 응답을 받은 클라이언트(브라우저)는 보낸 Origin과 서버로 부터 받은 Access-Control-Allow-Origin을 비교한다. 유효하면 리소스를 가져와서 쓰고, 유효하지 않으면 응답을 버리게 되고 CORS에러가 발생한다!

위 과정을 토대로 CORS에러는 서버에서 백엔드 개발자가 Access-Control-Allow-Origin헤더에 허용할 출처를 적용하면 해결이 된다는 것을 알아내었다! 아래에 Access-Control-Allow-Origin을 포함한 서버측에서 설정하는 헤더 종류가 있다.

Access-Control-Allow-Origin: * // 모든 출처 허용
Access-Control-Allow-Origin: https://example.com //이 출저만 허용
Access-Control-Allow-Methods: GET, POST, PUT // 요청을 허용할 HTTP 메서드를 설정 할 수 있다.
Access-Control-Allow-Headers: Content-Type, Authorization // 클라이언트가 요청에 포함할 할 수 있는 헤더를 지정한다.

CORS의 3가지 작동 방식

예비 요청(Preflight Request)

브라우저가 본 요청을 보내기 전에 서버와 통신이 원활히 잘 되는지 확인하는 요청이다. 이 예비요청의 HTTP 메소드는 OPTIONS이다. 이 예비 요청은 보안을 강화하지만, 실제 요청에 걸리는 시간이 늘어나 성능에 악영향을 준다. 특히 API 요청이 많으면 예비 요청으로 인해 서버 요청이 2배가 되어 비용이 늘어난다. 이때 예비 요청을 브라우저 캐시를 이용해 Access-Control-Max-Age 헤더에 캐시될 시간을 지정하면 최적화를 할 수 있다.

단순 요청 (Simple Request)

예비 요청을 생략하고 바로 서버에 본 요청을 보낸 후 Access-Control-Allow-Origin로 CORS 위반을 확인 하는 방식이다. 단 예비요청을 생략할 수 있는 조건이 있다.
1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
2. Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width 헤더여야 한다.
3. Content-Type 헤더가 application/x-www-form-urlencoded, multipart/form-data, text/plain중 하나여야한다.
대부분 HTTP API 요청은 text/xml 이나 application/json 으로 통신하기 때문에 예비 요청이 있다.

인증된 요청(Credentialed Request)

클라이언트(브라우저)가 요철할 때 서버에게 자격 인증 정보(Credential)을 실어 보내는 것이다.
자격 인증 정보는 세션 ID가 저장된 쿠키(Cookie) 혹은 Authorization 헤더에 설정하는 토큰 값 등이다. 아래 프로세스를 따른다.

1. 클라이언트에서 인증 정보를 보내도록 설정하기

Credential을 설정하지 않으면 브라우저가 제공하는 요청 API는 인증 데이터를 요청에 데이터에 담지 않는다. Credential 옵션은 3가지 값을 사용할 수 있다.

  • same-origin(기본값)
    같은 출처 간 요청에만 인증 정보를 담을 수 있다.
  • include
    모든 요청에 인증 정보를 담을 수 있다.
  • omit
    모든 요청에 인증 정보를 담지 않는다.

credentials 옵션 지정 예시 코드

// fetch 메서드
fetch("https://example.com:1234/users/login", {
	method: "POST",
	credentials: "include", 
    body: JSON.stringify({
        userId: 1,
    }),
  
// axios 라이브러리
axios.post('https://example.com:1234/users/login', { 
    profile: { username: username, password: password } 
}, { 
	withCredentials: true
})
})

2. 서버에서 인증된 요청에 대한 헤더 설정

  • 응답 헤더의 Access-Control-Allow-Credentials 항목을 true로 설정
  • 응답 헤더의 Access-Control-Allow-Origin 의 값에 와일드카드 문자("*")는 사용불가
  • 응답 헤더의 Access-Control-Allow-Methods 의 값에 와일드카드 문자("*")는 사용불가
  • 응답 헤더의 Access-Control-Allow-Headers 의 값에 와일드카드 문자("*")는 사용불기

참고 블로그: 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

profile
발전하는 프론트엔드 개발자입니다!

0개의 댓글