CORS(Cross Origin Resource Sharing)
1. 동일 출처 정책 (Same-Origin Policy)
우선 동일 출처 정책(Same-Origin Policy)에 대해서 알아보겠습니다.
MDN Web Docs
동일 출처 정책은 어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호 작용하는 것을 제한하는 중요한 보안 방식입니다. 동일 출처 정책은 잠재적으로 해로울 수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄여줍니다.
1.1. 동일 출처란?
두 URL의 프로토콜(protocol), 호스트(host), 포트(port)가 모두 같아야 동일한 출처입니다.
동일 출처에 대한 간단한 예시를 들어보겠습니다.
아래 표는 웹 어플리케이션이 http://store.company.com
(기본 포트, 80) 프로토콜, 호스트, 포트 정보를 가질 때 동일 출처 정책 상에서 요청의 성공 여부입니다.
동일 출처 정책에 따른 요청 성공 여부
URL | 결과 | 이유 |
---|---|---|
http://store.company.com/dir2/other.html | 성공 | 경로만 다름 |
http://store.company.com/dir/inner/another.html | 성공 | 경로만 다름 |
https://store.company.com/secure.html | 실패 | 프로토콜 다름 |
http://store.company.com:81/dir/etc.html | 실패 | 포트 다름 (http://는 80이 기본값) |
http://news.company.com/dir/other.html | 실패 | 호스트 다름 |
2. 교차 출처 자원 공유 (CORS, Cross Origin Resource Sharing)
MDN Web Docs
교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 어플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다.
프론트엔드 서비스와 백엔드 서비스를 구분하는 패턴은 동일 출처 정책을 위반하는 문제가 자주 발생합니다. 하나의 서버에서 웹 페이지와 데이터를 모두 가져올 때는 문제가 없었습니다. 프론트엔드 서비스와 백엔드 서비스가 나뉘면서 브라우저(browser)가 프론트엔드 서비스로부터 웹 페이지, 스타일, 스크립트 문서를 받고, 데이터나 리소스는 백엔드 서비스로부터 직접 받으려고 할 때 동일 출처 정책을 위반합니다.
교차 출처 자원 공유(CORS)는 이런 동일 출처 정책으로 발생하는 불편함을 해결하기 위해 예외 상황을 열어 두는 정책입니다. 브라우저가 직접 다른 출처의 리소스를 가져올 때 해당 출처에서 올바른 CORS 헤더(header) 정보를 포함한 응답을 받으면 동일 출처가 아니더라도 이를 허용합니다. 브라우저가 올바른 CORS 헤더 정보를 받지 못하면 이는 CORS 정책 위반이므로 받은 응답을 사용하지 않습니다.
동일 출처와 교차 출처를 간단히 살펴보겠습니다.
https://domain-a.com/
와https://domain-b.com/
주소를 가진 서버가 두 개 존재합니다.- 브라우저 주소 창에
https://domain-a.com/
가 입력되면 프론트엔드 서비스에 웹 페이지, 스타일, 스크립트 등의 파일을 전달받고 화면에 보여줍니다. - 현재 브라우저가 보여주는 화면의 출처는
https://domain-a.com/
입니다. - 브라우저가 화면에 보이는 첫 번째 이미지는
https://domain-a.com/image.png
주소를 통해 전달받습니다.- 이는 프로토콜, 호스트, 포트 번호가 같으므로 동일 출처입니다.
- 브라우저가 정상적으로 동작합니다.
- 브라우저가 화면에 보이는 두 번째 이미지는
https://domain-b.com/image-b.png
주소를 통해 전달받습니다.- 이는 호스트 정보가 다르므로 교차 출처입니다.
- 교차 출처 서버로부터 올바른 CORS 헤더 정보를 받으면 브라우저는 에러를 발생시키지 않습니다.
- 브라우저는 올바른 CORS 헤더 정보를 받지 못하면 해당 응답을 버리고 CORS 정책 위반 에러를 발생시킵니다.
3. CORS 작동 방식
간단하게 CORS 작동 방식을 정리해보겠습니다. 일부만 정리하였으며 구체적인 내용은 교차 출처 리소스 공유 (CORS)에서 확인할 수 있습니다.
3.1. 단순 요청 (Simple Requests)
이런 단순 요청은 아래 조건들을 만족해야 합니다.
- 메소드(method)
- GET
- HEAD
- POST
- 허용되는 Content-Type 헤더
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- 유저 에이전트(user agent)가 자동으로 설정한 헤더 외에 허용되는 헤더
- Accept
- Accept-Language
- Content-Language
- Content-Type
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
3.1.1. 단순 요청 시나리오
- 브라우저는
https://foo.example
서버로부터 웹 페이지를 전달받았습니다. - 브라우저가 전달받은 웹 페이지에서
https://bar.other
서버의 리소스를 호출합니다.- 브라우저는 자신이 보여주고 있는 페이지의 출처 정보를 헤더에
Origin: foo.example
값으로 함께 전달합니다.
- 브라우저는 자신이 보여주고 있는 페이지의 출처 정보를 헤더에
- 브라우저는
https://bar.other
서버로부터 원하는 컨텐츠와Access-Control-Allow-Origin: *
응답 헤더를 받습니다.- 특정 도메인의 요청만 허용하려면
Access-Control-Allow-Origin: https://foo.example
와 같이 허용할 도메인을 명시합니다.
- 특정 도메인의 요청만 허용하려면
- 브라우저는
https://bar.other
서버로부터 전달받은 리소스를 화면에 보여줄 수 있습니다.
3.1.2. 단순 요청과 응답 예시
요청
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
응답
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…XML Data…]
3.2. 프리플라이트 요청 (Preflight Requests)
단순 요청과 달리 먼저 OPTIONS
메소드를 통해 다른 도메인의 리소스로 HTTP 요청을 보내 실제 요청이 전송하기에 안전한지 확인하는 방법입니다.
Cross-Site
요청은 유저 데이터에 영향을 줄 수 있기 때문에 미리 전송(preflighted)해보는 방식입니다.
3.2.1. 프리플라이트 요청 시나리오
이미지에 포함된 도메인을 https://bar.other/
으로 대체하여 설명을 참조하시길 바랍니다.
- 브라우저는
https://foo.example
서버로부터 웹 페이지를 전달받았습니다. - 브라우저가 전달받은 웹 페이지에서
https://bar.other/
서버의 리소스를 위해 프리플라이트 요청을 보냅니다. - CORS 헤더와 정상 응답을 받으면 본 요청을 보냅니다.
3.1.2. 프리플라이트 요청과 응답 예시
요청
- 프리플라이트 요청을 보내면 다음과 같은 정보를 확인할 수 있습니다.
- 실제 요청에서 사용할 메소드
- Access-Control-Request-Method: POST
- 실제 요청에서 사용할 헤더 정보
- Access-Control-Request-Headers: X-PINGOTHER, Content-Type
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
응답
- 프리플라이트 응답을 보면 다음과 같은 정보를 확인할 수 있습니다.
- 허용되는 도메인
- Access-Control-Allow-Origin: https://foo.example
- 허용되는 메소드
- Access-Control-Allow-Methods: POST, GET, OPTIONS
- 허용되는 헤더 정보
- Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
- 프리플라이트 요청 결과를 캐시할 수 있는 시간
- Access-Control-Max-Age: 86400
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
CLOSING
CORS 정책 위반으로 개발에 불편함이 발생하는데, 이를 해결하기 위한 방법은 두 가지 있습니다.
- 프론트엔드 서비스의 프록시 기능을 사용하여 교차 호출이 발생하지 않도록 우회
- 백엔드 서비스에서 CORS 허용 헤더를 응답
아래 포스트들은 CORS 정책 위반이 발생하지 않도록 백엔드 서비스와 프론트엔드 서비스를 구현한 예제입니다. 자세한 내용은 관련 포스트를 참조하시길 바랍니다.
댓글남기기