CORS의 필요성과 동작과정

kaitlin_k·2022년 1월 15일
0

CS

목록 보기
12/19
post-thumbnail

SOP VS CORS

CORS (Cross-Origin Resource Sharing)
SOP (Same-Origin Policy)

🙋SOP (Same-Origin Policy)는 무엇일까?

SOP는 같은 오리진에서만 리소스를 공유할 수 있는 브라우저의 정책이다. 그런데, 웹 환경에서는 다른 오리진에 있는 리소스를 가져와 사용하는 일은 흔하기 때문에 예외조항을 만들었다. 그 예외조항 중 하나가 CORS 정책을 지킨 리소스 요청이다.

즉, 브라우저의 CORS 정책은 SOP을 지키기 위한 예외조항이라고 할 수 있다. CORS 정책을 지킨 요청만이 다른 오리진에서 리소스를 사용할 수 있다.


🙋CORS에서 Origin은 무엇일까?

URL은 protocol + domain(host) + port + path + parameters + anchor로 구성되어 있는데, 그 중에서 Origin은 프로토콜, 도메인, 포트를 합쳐놓은 것을 의미한다. 그런데, 포트는 http 프로토콜의 경우 80번을 사용하고, https는 443번으로 지정되어있으므로 생략할 수 있다.

여기서 URL(Uniform Resource Locator)이란, 서버에 자원이 어디에 위치하고 있는지를 알려주며, URI(Uniform Resource Identifier)는 서버에 저장된 자원의 식별자이다. 따라서, 엄밀히 말하면 URL은 path까지를 뜻하고, URI는 URL의 상위개념으로 path 뒤의 parameters 부분까지 포함한다.

즉, Same Origin은 URL 중 프로토콜, 도메인, 포트가 모두 동일한 출처를 말한다. Cross Origin은 URL 중 프로토콜, 도메인, 포트 중 하나라도 다른 출처를 말한다.

CORS (Cross-Origin Resource Sharing)

위에서 설명했듯이 브라우저는 기본적으로 SOP정책을 따른다. 하지만,
웹 폰트를 사용하거나, A,B,C.com에서 네이버 API 또는 카카오 API로 요청을 보내는 등 다른 도메인을 가진 API 서버에 요청을 보내는 경우들이 많기 때문에 다른 오리진으로의 요청을 안전하게 허용할 필요성이 생겼다. 따라서, CORS 정책으로 다른 오리진의 요청을 제한적으로 허용하였다.

🙋만약 어떠한 제약없이 모든 출처에 대한 요청을 열어놓는다면 어떤 문제가 발생할까?

유저 김코딩이 웹사이트 A에 로그인하여 토큰이 브라우저의 쿠키에 저장되어있다고 해보자. 이때, 김코딩이 개인정보를 탈취하기 위해 악의적으로 만들어진 웹사이트 B에 접속했을 경우, 웹사이트 B의 자바스크립트가 브라우저에 다운로드 받아 실행된다.

이때, 다운받아진 자바스크립트 코드로 웹사이트 B에서 브라우저에 저장된 웹사이트 A의 토큰을 사용해 웹사이트 A의 서버로부터 김코딩의 정보를 탈취해 웹사이트 B의 서버로 보낼 수 있는 것이다.


🙋CORS 옵션으로 wildcard를 사용하면 모든 출처를 허용한다는 것 아닌가?

CORS 옵션으로 wildcard(*)를 사용하면 오리진에 제한없이 요청을 보내고 브라우저에서 사용할 수 있다. 하지만, 브라우저의 쿠키 등 인증정보를 포함한 요청은 제한된다. 따라서, CORS 옵션이 wildcard인 경우에는 요청에 {withCredentials: true}를 사용할 수 없다.

그러므로, 쿠키 등의 브라우저에 저장된 인증정보 사용하는 경우, 서버에서는 wildcard가 아니라 허용할 오리진을 명시해두고, Access-Control-Allow-Credentials 항목을 true로 맞춰두어야한다.


🙋CORS 옵션 동작 원리는 무엇일까?

  • preflight 예비요청

    • 클라이언트는 OPTIONS 메소드를 사용해 다른 도메인의 리소스로 HTTP 요청을 보내 실제 요청을 전송하기에 안전한지 확인한다. 이때 요청의 Access-Control-Request-Method헤더에는 실제 요청에서 사용할 메소드를 명시하고, Access-Control-Request-Headers 헤더는 실제 요청할때, X-PingOtherContent-Type 사용자 정의 헤더와 함께 전송된다는 것을 서버에 알려준다.
      👉 클라이언트는 Origin 값으로 출처를 밝히고, Access-Control-Request- 등의 헤더로 실제 요청에서 사용할 메소드와 사용자 정의 헤더를 서버에 미리 알려준다.
    • 서버는 Access-Control-Allow-Origin으로 허용하는 출처를 명시한다. Access-Control-Allow-MethodsGET,POST,OPTIONS 메소드를 허용한다고 명시한다. Access-Control-Allow-HeadersX-PINGOTHER, Content-Type을 명시해 실제 요청에 헤더를 사용할 수 있음을 확인한다.
      👉 서버는 Access-Control-Allow- 등의 헤더로 서버에서 실제 요청에 대해 허용할 오리진, 메소드와 사용자 정의 헤더 등을 클라이언트에 응답한다.
    • 다시 클라이언트는 자신의 Origin과 응답의 Access-Control-Allow-Origin이 일치하는지 확인하고, 만약 다르다면 CORS ERROR가 발생한다. 만약, 두 값이 동일하다면 클라이언트는 메인 요청을 보낸다.
  • simple request
    다음 조건을 만족하는 요청들은 preflight를 보내지 않고 메인 요청을 보낸다.

    • 첫째, GET, HEAD, POST 중 하나의 요청인 경우
    • 둘째, 유저 에이전트가 자동으로 설정한 헤더 외에 수동으로 설정한 헤더가 Fetch 명세에서 "CORS-safelist request header"로 정의한 헤더들인 경우
    • 셋째, Content-Type이 application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나인 경우
    • 넷째, 요청에 사용된 XMLHttpRequestUpload 객체에 이벤트 리스너가 등록되어있지 않은 경우
    • 다섯째, 요청에 ReadableStream 객체가 사용되지 않은 경우
      👉위의 조건들을 만족하는 요청의 경우, 다른 오리진에서 요청을 보내더라도 서버의 응답헤더의 Access-Control-Allow-Origin에 클라이언트의 오리진 값이 포함되어있다면, 클라이언트에서는 받아온 데이터를 사용할 수 있다. 하지만, 클라이언트 오리진 값이 없다면, 클라이언트에서는 받아온 데이터를 사용할 수 없고, CORS ERROR가 발생한다.

NodeJS 기반의 웹 서버에서 CORS 설정하기

NodeJs에서 서버를 프로그래밍할때 사용하는 Express 프레임워크에서는 CORS 미들웨어를 사용할 수 있다. CORS 옵션은 사용자가 정의해서 사용할 수 있다.

  • origin
    응답 헤더의 Access-Control-Allow-Origin 값을 설정한다.

    • boolean: true이면 요청의 오리진을 허용하고, false일 경우 차단한다.
    • string: 특정 오리진을 명시할 수 있다.
    • RegExp: 정규표현식으로 명시할 수 있다.
    • array: 여러 오리진을 배열의 요소로 작성할 수 있다. 이때, 오리진은 문자열 또는 정규표현식으로 작성할 수 있다.
    • function: 함수로 작성할 수 있으며, 이때 첫번째 인자는 요청 오리진이고 두번째 인자는 콜백함수이다.
  • methods
    CORS 헤더의 Access-Control-Allow-Methods 값을 설정한다. GET, PUT, POST와 같이 문자열과 쉼표를 사용하거나 [GET, PUT, POST]와 같이 배열로 작성할 수 있다.

  • allowedHeaders
    CORS 헤더의 Access-Control-Allow-Headers 값을 설정한다. methods와 마찬가지로 문자열 또는 배열의 형태로 작성할 수 있다. 기본값은 요청 헤더의 Access-Control-Request-Headers 부분과 동일하게 적용된다.
    ex. "allowedHeaders": ['Content-Type', 'Authorization']

  • exposedHeaders
    CORS 헤더의 Access-Control-Expose-Headers 값을 설정한다. methods와 마찬가지로 문자열 또는 배열의 형태로 작성할 수 있다.
    ex. "exposedHeaders": ['Content-Range', 'X-Content-Range']

  • credentials
    CORS 헤더의 Access-Control-Allow-Credentials 값을 설정한다. Access-Control-Allow-Origin가 wildcard(*)인 경우에는 credentials 값에 관계없이 요청이 제한된다. 반변, Access-Control-Allow-Origin에 특정 오리진이 명시된 경우, credentials를 true로 설정하면 브라우저의 인증정보가 저장된 쿠키를 사용할 수 있다. 작성하지 않으면 옵션에 포함되지 않는다.

  • maxAge
    CORS 헤더의 Access-Control-Max-Age 값을 설정한다. preflight 요청을 보내 서버로부터 허가를 받은 경우 이 허락정보를 캐싱해놓고, 일정 기간동안 preflight 요청을 생략하고 요청과 응답을 받을 수 있다. 여기서 몇 초간 허가정보를 캐싱할지 설정할 수 있다. 정수 데이터타입으로 작성하며, 작성하지 않는 경우 옵션에 포함되지 않는다.

  • preflightContinue
    preflight 응답을 다음 핸들러에 전달한다. 기본값은 false이다.

  • optionSuccessStatus
    OPTIONS 요청 성공시 전달하는 상태코드 기본값은 204이다.

//CORS 옵션 기본 설정값
{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "preflightContinue": false,
  "optionsSuccessStatus": 204
}

//app.use(cors())로 요청에 대한 CORS 허용 (CORS 옵션 기본값 적용) 
const express = require('express')
const cors = require('cors')
const app = express()

app.use(cors())

app.get('/products/:id', function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for all origins!'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

//CORS 옵션 설정하기
const express = require('express')
const cors = require('cors')
const app = express()

const corsOptions = {
  origin: 'http://example.com',
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}

app.get('/products/:id', cors(corsOptions), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for only example.com.'})
})

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

reference

profile
어제보다 나은 오늘을 만드는 중

0개의 댓글