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 메서드를 통해 다른 도메인의 리소스에 요청이 가능한지 확인하는 작업이다.

  • 위의 플로우 차트 설명
    1. 자바스크립트의 fetch API를 사용하여 브라우저에게 리소스를 받아오라고 명령을 내리면, 브라우저는 서버에게 예비 요청을 먼저 보낸다.
    2. 서버는 예비 요청에 대한 응답으로 자신이 어떤 것들을 허용하고, 어떤 것들을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내준다.
    3. 이후 브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 허용 정책을 비교한 후, 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보낸다.
    4. 서버는 본 요청에 대한 응답을 하고, 브라우저는 최종적으로 이 응답 데이터를 자바스크립트에게 넘겨준다.
  •  Preflight request form

  • Preflight response form
    • 응답 코드는 200대 여야 하고, 응답 바디는 비어있는 것이 좋다.

2) Simple Request

  • 예비 요청을 보내지 않고 바로 서버에게 본 요청을 보낸 후, 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin과 같은 값을 보내주면, 그 때 브라우저가 CORS 정책 위반 여부를 검사하는 방식
  • 즉 프리플라이트와 단순 요청 시나리오는 전반적인 로직 자체는 같되, 예비 요청의 존재 유무만 다르다.
  • 심플 요청은 특정 조건을 만족하는 경우에만 사용할 수 있는데, 조건이 까다로워 조건을 충족시키기 어렵다.
  • 조건
    1. GET, POST, HEAD 메서드
    2. Content-Type (셋 중에 하나여야함)
      • A. application/x-www-from-urlencoded
      • B. multipart/form-data
      • C. text
    3. 헤더는 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

https://evan-moon.github.io/2020/05/21/about-cors/

https://inpa.tistory.com/entry/NODE-%F0%9F%93%9A-CORS-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-cors-%EB%AA%A8%EB%93%88