CORS(교차 출처 리소스 공유), SOP(동일 출처 정책)
CORS란?
Cross-Origin Resource Sharing, 즉 교차 출처 리소스 공유이다. (잘 안 외워진다..)
결론부터 말하자면, CORS는 HTTP 헤더(Access-Control-Allow-Origin)를 사용하여 한 출처에서 다른 출처의 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.
CORS 정책을 위반하면 위와 같은 CORS policy 오류 메세지가 뜬다.
여기서 출처란 무엇이고, 교차 출처는 또 무엇일까?
출처
출처(Origin)란 URL구조의 protocol, host, port
를 합친 것을 말한다.
콘솔 창에서 location.origin
을 치면 출처를 확인할 수 있다.
동일 출처라는 조건을 만족하려면 protocol, host, port가 동일해야 한다.
동일 출처 정책
Same-Origin Policy(SOP)는 같은 출처에서만 리소스를 공유할 수 있다는 규칙을 가진 보안 정책이다.
당연한 말이지만 동일 출처 정책을 사용하면 보안성이 높아진다.
하지만 웹 개발을 하다보면 외부에서 데이터를 요청해야 할 때가 있는데, 출처가 다르더라도 CORS 정책을 지킨 리소스 요청은 SOP 정책에서 예외로 허용해준다.
다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 하는 것이다.
CORS의 동작원리
- 기본적으로 웹에서 다른 출처의 리소스를 요청할 때 HTTP 프로토콜을 사용하는데, 이때 브라우저는 요청 헤더의 Origin 필드에 출처를 담아 보낸다.
- 이후 서버는 응답 헤더의
Access-Control-Allow-Origin
값에 이 리소스를 접근하는 것이 허용된 출처를 내려준다. - 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의
Access-Control-Allow-Origin
을 비교하여 유효한 응답인지를 결정한다.
여기서 중요한 것은 출처를 비교하는 로직이 브라우저에 구현되어 있는 스펙이라는 것이다.
CORS 정책을 위반하더라도 서버는 정상적으로 응답(status 200)을 보내고, 브라우저 측에서 출처를 체크한다.
CORS의 동작방식
Preflight Request
가장 일반적인 방식으로, 브라우저에서 요청을 한번에 보내지 않고 예비 요청(Preflight)과 본 요청으로 나누어서 서버로 전송한다.
예비 요청에는 HTTP 메소드 중
OPTIONS
메소드가 사용된다.요청을 보내기 전에 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인한다.
이후 브라우저는 자신이 보낸 예비 요청과 서버가 응답한 허용 정책을 비교한 후, 이 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보낸다.
Simple Request
단순 요청은 예비 요청을 보내지 않고 본 요청을 바로 보낸다.
아래 조건을 만족하는 경우에만 예비 요청을 생략할 수 있다.
- HTTP 메서드 중
GET
,HEAD
,POST
를 사용하는 경우 - 자동으로 설정되는 헤더 외에 수동으로 설정할 수 있는 헤더는
Accept
,Accept-Language
,Content-Language
,Content-Type
뿐이다. Content-Type
헤더에는application/x-www-form-urlencoded
,multipart/form-data
,text/plain
값만 허용된다.
- HTTP 메서드 중
보통 웹에서 요청을 보낼 때
Content-Type
헤더에text/xml
이나application/json
을 설정해야 하기 때문에 사실상 조건을 충족시키기 어렵다.
CORS 해결방법
Access-Control-Allow-Origin 세팅
서버에서
Access-Control-Allow-Origin
헤더에 알맞은 값을 세팅해준다.와일드카드인
*
으로 세팅하면 보안성이 취약해지므로 출처를 명시해주어야 한다.서버에서 미들웨어 등을 사용하여 접근 가능하도록 세팅할 수 있다.
Webpack Dev Server로 리버스 프록싱
로컬에서 개발을 하는 경우 webpack-dev-server
가 제공하는 프록시 기능을 사용하면 CORS 정책을 우회할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
module.exports = {
devServer: {
proxy: {
"/api": {
target: "https://api.evan.com",
changeOrigin: true,
pathRewrite: { "^/api": "" },
},
},
},
};
위와 같이 devServer.proxy
를 설정하면 /api
로 요청을 보냈을 때 웹팩에서 localhost:8000/api
를 https://api.evan.com
로 프록싱해주기 때문에 마치 CORS 정책을 지킨 것처럼 서버와 자유롭게 통신을 할 수 있다.
express로 서버를 실행하는 경우 http-proxy-middleware
라이브러리를 사용하면 된다.
추가
SOP는 HTML 문서 안에 포함된 image, css, script에는 적용되지 않는다.
하지만 CORS를 통과해도 응답에 담긴 내용에는 접근할 수 없다.
1
2
<img src="https://evanmoon.tistory.com/rss" />
<script src="https://evanmoon.tistory.com/rss"></script>