CORS
란URL의 scheme(프로토콜), host(도메인), port(포트)로 정의된다.
이 세 가지가 모두 같은 경우 same-origin이라고 한다.
아래의 두 URL은 같은 출처이다.
즉, 이 3가지 중 하나라도 다르면 cross-origin 이라고 하는 것이다.
아래는 모두 다른 출처이다.
다른 스킴 | 다른 호스트 | 다른 포트 |
---|---|---|
http://example.com/app1 | http://www.example.com | http://example.com:8080 |
https://example.com/app2 | http://myapp.example.com | http://example.com |
SOP(Same-Origin Policy)
❓왜 이런 보안 규칙들이 만들어진 것인가?
- 웹페이지를 생각해 보면, 누구나 개발자 도구만 열어도 어떻게 DOM이 작성되어 있는지와 같은 정보들을 확인할 수 있다.
- 따라서 악의를 가지고 script등을 이용하여 사용자의 정보를 빼갈 수도 있기 때문이다.
❓ 그럼 CORS는 어떻게 동작하는 걸까?
CORS
의 동작 원리HTTP
프로토콜을 사용하여 요청하는데, 이때 브라우저는 요청 헤더 (request header)에 Origin 필드에 요청을 보내는 출처를 담아 전송한다.Access-Control-Allow-Origin
이라는 값에 '이 리소스를 접근하는 것이 허용된 출처'를 내려준다. 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin
과 서버가 보내준 응답의 Access-Control-Allow-Origin
을 비교해 본 후 이 응답이 유효한 응답인지 아닌지를 결정한다.HTTP 메소드들 중 GET 이 외의 메소드나, POST 메소드에서 특정 MIME Type은 서버 데이터에 사이드 이펙트를 발생시킬 수 있기 때문에 기본적으로 브라우저는 Preflight Request 방식으로 요청할 수 있도록 규제한다.
CORS는 총 3가지의 시나리오를 가진다.
Simple Request
Access-Control-Allow-Origin
과 같은 값을 보내주면 그때 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다.
- HTTP Method가
GET
,POST
,HEAD
이셋 중에 하나여야 한다.- Fetch 명세에서
"CORS-safelisted request-header"
로 정의한 헤더들인Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width
를 제외한 헤더를 사용하면 안된다.- 만약
Content-Type
를 사용하는 경우에는application/x-www-form-urlencoded, multipart/form-data, text/plain
만 허용된다.
Authoriztion
헤더만 하더라도 저 조건을 지킬 수 없다.text/xml
이나 application/json
컨텐츠 타입을 가지도록 설계 되기 때문에 조건을 충족시키기 어렵다.Prefilght Request
Preflight
라고 부르고, Preflight
에는 HTTP
메소드 중 OPTIONS
메소드가 사용된다. (Preflight
의 역할은 본 요청을 보내기 전에 브라우저 스스로 이 요청을 보내는 것이 안전한지 판단하는 것이다.)fetch API
를 사용하여 브라우저에게 리소스를 받아오라는 명령을 내리면 브라우저는 서버에게 예비 요청을 먼저 보내고, 서버는 이 예비 요청에 대한 응답으로 현재 자신이 어떤 것들을 허용하고, 어떤 것들을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내주게 된다.const headers = new Headers({
'Content-Type': 'text/xml',
});
fetch('https://evanmoon.tistory.com/rss', { headers });
OPTIONS https://evanmoon.tistory.com/rss
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en->US,en;q=0.9,ko;q=0.8,ja;q=0.7,la;q=0.6
// preflight요청을 할 때 실제 요청에서 어떤 header를 사용할 것인지 서버에게 알리기 위해 사용
Access-Control-Request-Headers: content-type
// preflight 요청을 할 때 실제 요청에서 어떤 메서드를 사용할 것인지 서버에게 알리기 위해 사용
Access-Control-Request-Method: GET
Connection: keep-alive
Host: evanmoon.tistory.com
Origin: https://evan-moon.github.io
Referer: https://evan-moon.github.io/2020/05/21/about-cors/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Preflight
는 자신이 예비 요청 이후에 보낼 본 요청에 대한 다른 정보들도 함께 포함되어 있다. 이렇게 요청을 보내면 서버에서 Preflight
에 대한 응답을 보낸다.OPTIONS https://evanmoon.tistory.com/rss 200 OK
// 브라우저가 해당 origin이 자원에 접근할 수 있도록 허용.
// 혹은 *은 credentials이 없는 요청에 한해서 모든 origin에서 접근이 가능하도록 허용한다.
Access-Control-Allow-Origin: https://evanmoon.tistory.com
Content-Encoding: gzip
Content-Length: 699
Content-Type: text/xml; charset=utf-8
Date: Sun, 24 May 2020 11:52:33 GMT
P3P: CP='ALL DSP COR MON LAW OUR LEG DEL'
Server: Apache
Vary: Accept-Encoding
X-UA-Compatible: IE=Edge
Access-Control-Allow-Origin
값이 존재하면, CORS 정책 위반이 아니다.Credentialed Request
XMLHttpRequest
객체나 fetch API
는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다. 이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 credentials
옵션이다. 옵션은 3가지가 있다.옵션 값 | 설명 |
---|---|
same-origin (기본값) | 같은 출처 간 요청에만 인증 정보를 담을 수 있다 |
include | 모든 요청에 인증 정보를 담을 수 있다 |
omit | 모든 요청에 인증 정보를 담지 않는다 |
same-origin
이나 include
와 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함된다면, 이제 브라우저는 다른 출처의 리소스를 요청할 때 단순히 Access-Control-Allow-Origin
만 확인하는 것이 아니라 좀 더 꼼꼼하게 검사하게 된다.-요청에 인증정보가 담겨있는 상태에서 다른 출처의 리소스를 요청하게 되면 브라우저는 CORS정책 위반 여부를 검사하는 룰에 다음 두가지를 추가하게 된다.
Access-Control-Allow-Origin : *
이면 안 되며, 명시적인 URL이어야 한다.- 응답 헤더에는 반드시
Access-Control-Allow-Credentials:true
가 존재해야 한다.
CORS
해결 방법Access-Control-Allow-Origin : *
혹은 Access-Control-Allow-Origin: 허용하고자 하는 도메인
값을 주는 방법이다.-프론트엔드 개발자는 대부분 웹팩과 webpack-dev-server를 사용하여 자신의 머신에 개발 환경을 구축하게 되는데, 이 라이브러리가 제공하는 프록시 기능을 사용하면 아주 편하게 CORS 정책을 우회할 수 있다.
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'https://api.evan.com',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
}
}
}
npm install --save cors
yarn add cors
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors()); // CORS 미들웨어 추가
...
하지만 위에 처럼 헤더를 추가 하거나 미들웨어를 적용하면 모든 요청에 대해 허가를 하게 된다. -> 보안적으로 취약해질 수 있음.
그래서 cors 미들웨어에는 여러가지를 설정할 수가 있다.
( https://www.npmjs.com/package/cors )
const corsOptions = {
origin: 'http://localhost:3000', // 허락하고자 하는 요청 주소
credentials: true, // true로 하면 설정한 내용을 response 헤더에 추가
};
app.use(cors(corsOptions)); // config 추가
<reference>
https://developer.mozilla.org/ko/docs/Web/HTTP/CORS