본문 바로가기

기술들/Today I Learned

CORS 정책 정리

CORS정책에 대해 정리해보려고 한다. 아직 완벽히 이해가 되진 않았지만 정리하다 보면 이해 되겠지..!

 

CORS란?

CORS 정책은 서로 다른 origin간 데이터 교환이 이루어질 때, 이를 허용할지 말지를 결정하는 정책이다. 그럼 여기서 origin이란 무엇일까? 

 

origin : 프로토콜, 도메인, 포트번호로 구성된 주소 

 

이 세가지 요소 중 하나만 달라도 서도 다른 origin이고, 서로 다른 origin간 요청과 응답이 이루어 질 때, 크로스오리진 요청/응답(Cross Origin request/response) 라고 일컫는다. 

 

예를 들어보자. 

https://mingu.com:3000이라는 사이트(origin)에서 https://mingus.com:3001 이라는 서버(remote origin)에 어떤 데이터를 가져오기 위한 요청을 보낸다고 하면, 두 origin을 비교했을 때, 도메인의 이름이 다르고(mingu <=> mingus), 포트번호도 다르기 때문에 서로 다른 오리진이라고 볼 수 있다. 그렇다면 서버는 자신과 연관되어 있진 않은 origin에서 요청이 왔는데, 무엇을 믿고 데이터를 보내줘야 할까? 그렇기 때문에 서버는 자신의 데이터를 가져갈 수 있는 클라이언트(origin)를 헤더에 명시했고, 그 명시되어 있는 오리진에서만 데이터를 가져갈 수 있게 한다. 위 맥락들을 보면, CORS라는 개념이 보안(Security)과 관련되어 있음을 알 수 있다. 신뢰할 수 없는(=서버가 origin헤더에 명시하지 않은) 사이트에서 자신의 데이터를 못 가져 가게 하는 것이다. 

 

옛날에는 서로 다른 오리진 간 데이터 교환이 불가능했었다. form이나 script로 편법을 통해서만 서로 다른 오리진간 데이터 교환이 가능했었는데, 시간이 흘러 개발자들은 편법 말고 제대로 된 방법을 원했고, 이후 CORS라는 정책을 도입해서 데이터교환을 가능하게 만들었다. 

 

일단 여기까지 요약하면, 서로 다른 오리진(=cross origin)간 데이터 교환이 이루어질 때 CORS정책이 적용된다는 말이다.

 

더 나아가 크로스 오리진간 데이터 교환이 이루어질 때에도 다음과 같이 두 가지 방식으로 분류된다. 

 

안전한 크로스 오리진 요청

안전한 크로스 오리진 요청은 다음과 같은 조건을 만족할 때 이루어진다. 

 

1. 클라이언트(Origin)가 서버로(Remote Origin)으로 요청을 보낼 시, 사용된 HTTP 메소드가 GET, POST, HEAD 일 경우.

2. 요청 헤더에

  • Accept,
  • Accept Language 
  • Content-Language
  • Content-type 의 값이 multipart/form-data, text/plain, application/x-www-form-urlencoded 인 경우

위 두가지 조건을 모두 충족하면 안전한 크로스 오리진 요청이고, 만약 한 가지라도 충족하지 못한다면 안전하지 않은 크로스 오리진 요청으로 분류된다. 

 

안전한 크로스 오리진 요청의 Flow를 정리해보자. 

1. 클라이언트(origin) 에서 크로스 요청을 보내면 브라우저는, origin헤더에 값이 설정되어 있는지 확인한다. 설정이 잘 되어있으면 요청을 진행한다. 

2. 크로스 요청을 보내면 서버(remote origin)는 클라이언트의 오리진을 확인하고 Access-Control-Allow-Origin라는 헤더에 자신이 허용한 origin값을 할당해서 클라이언트로 응답한다. 

3. 브라우저는 응답에서 Access-Control-Allow-Origin에 *(와일드카드)나 클라이언트의 origin이 할당되어 있는지 확인하고, 할당되어 있으면 javascript가 응답에 접근할 수 있게 한다. 그렇지 않다면 에러를 발생시킨다. 

 

응답이 오면 javascript는 기본적인(안전한) 헤더에만 접근할 수 있다. 안전한 헤더는 다음과 같다. 

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

javascript가 위에 명시한 안전한 헤더 말고, 안전하지 않은 헤더에 접근하기 위해서는 서버에서

Access-Control-Expose-Headers로 특정한 안전하지 않은 헤더를 명시를 해줘야만 js가 접근할 수 있다. 

 

안전하지 않은 크로스 오리진 요청

본 요청은 위에 명시한 안전한 크로스 오리진 요청의 조건에 해당되지 않을 경우를 말한다. 

fetch(url, {
  method : 'PUT',
  headers : {
    Content-type : text/plain,
    CustomHeader : "aa"
  }
})

위 요청이 안전하지 않은 크로스 오리진 요청인 이유는, PUT메소드를 사용했고, 비표준 헤더(사용자가 만든 헤더)인 CustomHeader를 사용했기 때문이다. 

 

브라우저가 위 요청을 보내기 전,  메소드와 CustomHeader를 확인하고, Unsafe한 크로스 요청임을 알게 될 것이다. 그럼 여기서 브라우저는 위 본요청을 보내기 전 preflight(=사전요청)라는 요청을 보내 서버에게 안전하지 않은 크로스 요청을 보내겠다는 사전 요청을 보내게 된다.

 

이 경우 사전요청에는 OPTIONS메소드와 헤더엔

Access-Control-Request-Method : 'PUT'

Access-Control-Request-Headers : CustomHeader

를 포함시켜, 서버가 위 메소드와 헤더를 허용했는지를 확인한다. 

 

만약 서버가 허용하지 않은 조건이 있다면 본 요청은 보내지지 않는다. 만약 서버가 위 안전하지 않은 요청에 해당하는 조건을 모두 허용한 상태라면 다음과 같은 응답을 보낼 것이다. 

200 OK
Access-Control-Allow-Origin: url
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: CustomHeader
Access-Control-Max-Age: 86400

이렇게 응답이 오면 브라우저는 다시 본 요청을 보내 데이터 교환을 가능하게 한다. 

 

단순한 요청인줄 알았는데 이렇게 복잡한 절차가 숨어 있구나 싶었다. 항상 복잡한 이유는 보안과 관련된 것 같다. 일단 여기까지 정리한다 

 

참고

developer.mozilla.org/ko/docs/Web/HTTP/CORS

블로그

ko.javascript.info/fetch-crossorigin

 

 

'기술들 > Today I Learned' 카테고리의 다른 글

Cookie  (0) 2020.12.07
Node.js architecture 정리  (0) 2020.12.01
HTTP Header 정리 (HTTP/1.1 기준)  (0) 2020.11.22
HTTP 기초 정리  (0) 2020.11.20
비동기, 동기 정리 (feat. callback, promise, async/await)  (0) 2020.11.17