CORS

kirin.log·2021년 6월 13일
0
post-custom-banner

🐹 동일 출처 정책(Same-Origin Policy)

  • 한 출처(Origin)에서 불러온 문서(혹은 스크립트)가 다른 출처의 리소스를 사용하는 것을 제한하는 브러우저 보안정책.
  • 잠재적으로 해로울 수 있는 문서를 분리 (프로토콜, 호스트(도메인), 포트 모두 같아야 동일 Origin)
  • 출처가 다른 경우, 브라우저는 response를 받지 못하도록 막는다.

🐹 CORS (Cross Origin Resource Sharing)

  • 동일 출처 정책(Same-Origin Policy) 조건에 부합하지 않는 다른 출처(Origin)를 Cross-Origin이라 한다.

  • CORS도메인 또는 포트가 다른 서버의 자원을 요청하는 메커니즘이다.
    (=동일 Origin이 아닌 문서에 다른 출처의 리소스를 사용할 수 있도록 요청하는 것)

  • 추가 HTTP 요청헤더를 사용하여, 다른 출처간의 통신 허용하도록 하는 방법

  • 웹페이지는 Image(<img />), css(<link />), script(<script />), Iframe, 동영상에 대해서는 자유롭게 가져올 수 있다. 하지만, 스크립트 태그 내에서 특정한 도메인 간(Cross-Domain) 요청, 특히 Ajax 요청은 다른 도메인에 데이터를 요청하는 것이 금지되었다.

AJAX

AJAX는 Javascript Library 중 하나이며, Asynchronous Javascript And Xml의 약자이다.

브라우저가 가지고 있는 XMLHttpRequest 객체를 이용해서 전체 페이지를 새로고침하지 않고, 필요한 데이터만 비동기적으로 가져오는 방식이다.

이 경우 Json 이나 XML 형태로 데이터를 주고 받는다. 보통은 JQuery의 AJAX 또는 axios 라이브러리를 이용해서 GET, POST 등의 요청을 하는 식으로 많이 사용한다.

🤔 Cross Domain이 무엇인가?
REST API의 ‘URI의 Authority’ 와 관련하여, Authority서버명도메인명으로 구성된다.

여기서 도메인에 해당하는 부분은 userinfo@host:port 형식으로 구성된다. 보통 userinfo는 ssh 접속을 할 때 많이 사용되고, 우리가 보는 웹 페이지의 도메인은 host(ip 주소)와 port(포트 번호)일 것이다.

즉, Cross Domain은 ip 주소 또는 포트 번호가 다른 것을 의미한다.

❓ CORS가 왜 생겼고, 왜 이것을 따라야 할까?

기존에는 다른 도메인에 데이터를 요청하는 것 자체가 불가능했다. 하지만, Ajax가 점점 널리 쓰이게 되고, 요즘은 프론트엔드 개발을 “localhost:3000” 같이 다른 도메인에서 개발을 하듯이 타 도메인에 대해 데이터 요청이 필요하게 되었다.

Cross-Site HTTP Requests에 대한 요구가 늘어나자 W3C에서 CORS 라는 권고안을 내 준 것이다.

당연히 Cross-site HTTP Requests보안을 위해서 막아뒀었기 때문에 이를 허용해주는 정책인 CORS 권고안을 따라야 한다.

최초 Ajax 요청 시, GET, HEAD, POST요청이 아니거나 또는 POST 요청이라도 custom HTTP header가 포함되어 있거나,
표준으로 정한 content-type이 아닌 요청(application/x-www-form-urlencoded, mutipart/form-data, text/plain 외의 모든 type)이면 모두 Preflighted Request’ 라고 하는 OPTIONS 요청을 보내게 된다.


위 조건에 해당하는 경우 모두 OPTIONS 요청을 먼저 보낸다.
OPTIONS 요청에 대한 응답으로 Access-Control-* 형식의 헤더가 포함된 응답을 받게 된다.(Server 영역)

여기서 요청을 보낸 Origin, Method, Custom Header, Credential 등이 없다면, ajax 요청은 CORS 정책에 의해 거부된다.


🏈 크롬 실패 문구
No 'Access-Control-Allow-Origin' header is present on the requested resourse. Origin '[요청한 도메인]' is therefore not allowed access.

🏈 파이어폭스 실패 문구
교차 원본 요청 차단: 동일 출처 정책으로 인해 [요청한 도메인]에 있는 원격 자원을 읽을 수 없습니다. 자원을 같은 도메인으로 이동시키거나 CORS를 활성화하여 해결하라 수 있습니다.


🌈 동일 출처 정책(Same-Origin-Policy)

웹 애플리케이션 보안 모델에서 중요한 개념 중 하나가 동일 출처 정책(Same-Origin-Policy) 이다.

이 정책에 의해서 자바스크립트(XMLHttpRequest)로 다른 웹페이지에 접근할 때는 같은 출처(same-origin)의 페이지에만 접근이 가능하다. 같은 출처라는 것은 프로토콜, 호스트명, 포트 가 같다는 것을 의미한다.
즉, 쉽게말하면 웹페이지의 스크립트는 그 페이지와 같은 서버에 있는 주소로만 ajax요청을 할 수 있다는 것이다.


❗ CORS Error를 방지하려면?

  1. Chrome CORS plugin 사용
  • Chrome을 사용하고 있다면, 웹 스토어에서 CORS 플러그인을 다운로드 받아 사용하는 방법이 있다.
  • 하지만 이 방법의 경우에는 Access-Control-Allow-Origin: * 과 Access-Control-Allow-Method: 만 추가될 뿐 다른 것을 커스터마이징 할 수 없다.
    인증/인가를 위해서는 좀 더 커스터마이징이 필요하다.
  1. Server에서 CORS 허용 설정
    2-1. node.jsexpress를 사용하는 경우에는 cors 라는 미들웨어를 사용한다.
var express = require('express')
var cors = require('cors')  // 👆👆 미들웨어
var 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')
})
  1. Client에서 header요청(request)
    Request headers(클라이언트의 요청 헤더)
  • Origin: 요청을 보내는 페이지의 출처(도메인)
  • Access-Control-Request-Method: 실제 요청하려는 메서드
  • Access-Control-Request-Headers: 실제 요청에 포함되어 있는 헤더 이름
  1. JSON방식으로 요청
  • 웹브라우저에서 css나 js같은 리소스 파일들은 동일 출처 정책에 영항을 받지 않고 로딩이 가능하다. 이런 점을 응용해서 외부 서버에서 읽어온 js 파일을 json으로 바꿔주는 일종의 편법적인 방법이다. (package.json 파일추가 방법)
  • 단점은 리소스 파일을 GET메서드로 읽어오기 때문에 GET 방식의 API요청만 가능하다.
  1. CORS 미들웨어
  • 프록시(proxy): 직접 통신하지 못하는 두 개의 컴퓨터 사이에서 서로 통신할 수 있도록 돕는 역할을 프록시라고 한다.
  • 클라이언트 단에서 직접 proxy서버를 만들거나

📊 CORS Request의 종류 그리고 인증

CORS 관점에서 보는 요청의 종류는 4가지이다.

  • Simple Request
  • Preflighted Request
  • Credentialed Request
  • Non-Credential Request

👉 (1) Simple Request
보통은 CORS에 해당하면 Preflight(사전 요청) 라고 해서 OPTIONS method를 통해 이 요청이 전송하기에 안전한지를 체크하는 요청을 보낸다. (Preflighted Request)

그런데 이런 체크하는 행위를 할 필요 없는 요청을 보낼 때가 있다. 아래의 조건에 해당하는 케이스다.

  • GET, HEAD, POST 중 하나의 요청
  • Custom Header가 없는 경우 (아래의 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

👉 (2) Preflighted Request
Simple Request와 달리 OPTIONS method를 통해 Cross-site 요청에 대해 전송이 안전한지를 체크한다.

위에서 언급한 Simple Request의 조건에 해당하지 않는다면 아래와 같이 OPTIONS 요청을 보낸다.

// OPTIONS Request

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


// OPTIONS Response

HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
   // 요청(request)에 대해서 Origin, Methods, Headers 를 허용하겠다는 의미 
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
profile
boma91@gmail.com
post-custom-banner

0개의 댓글