Network & AWS
[CORS]란 무엇인가
Haksae
2022. 2. 9. 01:37
0. 선행 지식
- 웹 생태계에는 다른 출처로의 리소스 요청을 제한하는 것과 관련된 두 가지 정책이 존재한다. 바로 CORS와 SOP다.
- SOP(Same Origin Policy) : 다른 출처의 리소스를 사용하는 것을 제한하는 보안 방식
- Origin(출처) : URL의 Protocol, Host, Port를 통해 같은 출처인지 다른 출처인지 판단할 수 있다.
- Cross Origin : 다른 출처를 뜻한다.
- SOP를 통해 다른 origin을 걸러냄(동일출처만 허용함)으로써 보안에 도움을 준다.
- 만약 다른 출처의 리소스가 필요하다면, SOP를 푸어주는 CORS를 사용하는 것이다.
1. CORS (Cross-Origin Resource Sharing)
CORS란 자신이 속하지 않은 다른 도메인, 다른 프로토콜 혹은 다른 포트에 있는 리소스를 요청하는 cross-origin HTTP 요청 방식이다. 즉 다른 출처의 자원을 공유하고, 다른 출처의 선택한 자원에 접근할 수 있도록 권한을 부여하는 체제를 뜻한다.
- 웹 생태계가 다양해지면서 여러 서비스들 간에 보다 자유롭게 데이터를 주고 받아질 필요가 생겼다.
- 다른 사이트 간의 요청을 브라우저가 막고 있으니, 합의된 출처들 간에 합법적으로 이를 허용해주기 위해, 어떤 기준을 충족시키면 리소스 공유가 되도록 만들어진 매커니즘이 바로 CORS이다. (교차 출처 자원 공유 방식)
- 위의 그림을 보면, 왼쪽의 웹 사이트는 domain-a.com으로 구동되고 있다. 파란색 이미지는 웹 사이트가 구동 중인 domain과 동일한 domain-a.com로부터 자료를 요청하고 수신한다.
- 그러나 아래 빨간색 이미지는 domain-b.com이라는 다른 도메인에 자료를 요청하고 수신한다. 이러한 방식을 cross-origin HTTP 방식이라 한다
- 서버는 기본적으로 CORS 방식을 제한해둔다. 왜냐하면 특정 서버 리소스에 다른 임의의 웹 사이트들이 request를 보낼 수 있다면 악의적으로 특정 서버의 세션을 탈취하거나 서버에 무리가 가는 행위 등 안 좋은 행동을 할 수 있기 때문이다.
2. CORS는 어떻게 동작하는가?
- 기본적으로 웹 클라이언트 애플리케이션이 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜을 사용하여 요청을 보내게 되는데, 이 때 브라우저는 요청 헤더에 Origin이라는 필드에 요청을 보내는 출처를 함께 담아 보낸다.
Origin: https://evan-moon.github.io
- 이후 서버가 이 요청에 대한 응답을 할 때, 응답 헤더의 Access-Control-Allow-Origin이라는 값에 "이 리소스를 접근하는 것이 허용된 출처"를 내려주고, 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후, 이 응답이 유효한 응답인지 아닌지를 결정한다.
- 기본적인 흐름은 간단하지만, CORS가 동작하는 방식은 한 가지가 아니라 세 가지의 시나리오에 따라 변경된다.
1) Preflight Request (프리플라이트 요청)
- 일반적으로 웹 애플리케이션을 개발할 때 가장 많이 마주치는 시나리오
- 해당 시나리오 때에 브라우저는 요청을 한번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송한다.
- OPTIONS 메서드를 통해 다른 도메인의 리소스에 요청이 가능한지 확인하는 작업이다.
- 위의 플로우 차트 설명
- 자바스크립트의 fetch API를 사용하여 브라우저에게 리소스를 받아오라고 명령을 내리면, 브라우저는 서버에게 예비 요청을 먼저 보낸다.
- 서버는 예비 요청에 대한 응답으로 자신이 어떤 것들을 허용하고, 어떤 것들을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내준다.
- 이후 브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 허용 정책을 비교한 후, 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보낸다.
- 서버는 본 요청에 대한 응답을 하고, 브라우저는 최종적으로 이 응답 데이터를 자바스크립트에게 넘겨준다.
- Preflight request form
- Preflight response form
- 응답 코드는 200대 여야 하고, 응답 바디는 비어있는 것이 좋다.
2) Simple Request
- 예비 요청을 보내지 않고 바로 서버에게 본 요청을 보낸 후, 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin과 같은 값을 보내주면, 그 때 브라우저가 CORS 정책 위반 여부를 검사하는 방식
- 즉 프리플라이트와 단순 요청 시나리오는 전반적인 로직 자체는 같되, 예비 요청의 존재 유무만 다르다.
- 심플 요청은 특정 조건을 만족하는 경우에만 사용할 수 있는데, 조건이 까다로워 조건을 충족시키기 어렵다.
- 조건
- GET, POST, HEAD 메서드
- Content-Type (셋 중에 하나여야함)
- A. application/x-www-from-urlencoded
- B. multipart/form-data
- C. text
- 헤더는 Accept, Accept-Language, Content-Language, Content-Type만 허용된다.
3) Credentialbed Request (인증정보 포함 요청)
- 인증된 요청을 사용하는 방법으로, CORS의 기본적인 방식이라기 보다는 다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방식이다.
- 기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠기 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다. 이 때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 Credentialed 옵션이다.
- 이 옵션은 총 3가지의 값을 사용할 수 있다.
same-origin (기본값) 같은 출처 간 요청에만 인증 정보를 담을 수 있다 include 모든 요청에 인증 정보를 담을 수 있다 omit 모든 요청에 인증 정보를 담지 않는다 - 클라이언트 측 : credentials : include
- 서버측 : 인증모드가 include일 경우, 서버측은 아래와 같은 내용을 포함해야한다.
- Access-Control-Allow-Credentials : true
- (Access-Control-Allow-Origin: *은 안된다.)
3. Node.js로 CORS 방식 허용하기
1) 패키지 설치
$ npm i cors
2) Access-Control-Allow-Origin response 헤더를 추가.
어떤 특정 요청에만 적용 하고 싶다면 cross-origin을 허락하는 헤더를 추가해 문제를 해결할 수 있다.
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*"); // 모든 도메인
res.header("Access-Control-Allow-Origin", "https://example.com"); // 특정 도메인
});
app.use(cors({
origin: true,
credentials: true
}));
- origin: true는 프론트 도메인 주소가 자동으로 Access-Control-Allow-Origin에 들어간다. 와일드카드인 *와는 다르다.
- credentials는 Access-Control-Allow-Origin을 ture로 만들어주는 옵션이다.
3) 미들웨어로 모든 도메인 허용
const cors = require('cors');
app.use(cors());
4) 미들웨어로 특정 도메인만 허용
const cors = require('cors');
const corsOptions = {
origin: "http://localhost:3000",
credentials: true
}
app.use(cors(corsOptions));
const options = {
origin: "http://example.com", // 접근 권한을 부여하는 도메인
credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가
optionsSuccessStatus: 200, // 응답 상태 200으로 설정
};
app.use(cors(options));
*함수 형태로도 가능
const cors = require('cors');
const domains = ['http://localhost:3000'];
const corsOptions = {
origin: function(origin, callback){
const isTrue = domains.indexOf(origin) !== -1;
callback(null, isTrue);
}
,
credentials: true
}
app.use(cors(corsOptions));
참고자료
https://developer.mozilla.org/ko/docs/Web/HTTP/CORS
https://evan-moon.github.io/2020/05/21/about-cors/
https://surprisecomputer.tistory.com/32
https://firework-ham.tistory.com/70
https://handhand.tistory.com/35