Cors, Same-Origin-Policy, Proxy에 대해서 정리한 글입니다.

글 작성 계기

약 1년 전에 프로젝트를 하다가 Node.js로 구현한 백엔드 서버에 React 프레임워크로 구현한 프론트 엔드가 접근하려고 할 때 CORS 에러라는 것을 보았습니다. 그 당시에는 이것이 무엇인지 정확하게 알지는 못하고 구글링을 하여 http-proxy-middeware와 cors 모듈을 설치하여 해결하고 바로 넘어갔었습니다.

그래서 이번 기회에 정리하여 JS 웹 통신을 하면서 발생할 수 있는 CORS 에러에 대해서 정확하게 알아보고자 글을 작성하게 되었습니다.

CORS

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다. 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행합니다.

교차 출처 요청의 예시: https://domain-a.com 의 프론트 엔드 JavaScript 코드가 XMLHttpRequest를 사용하여 https://domain-b.com/data.json 을 요청하는 경우.

보안 상의 이유로, 브라우저는 스크립트에서 시작한 교차 출처 HTTP 요청을 제한합니다. 예를 들어, XMLHttpRequest와 Fetch API는 동일 출처 정책을 따릅니다. 즉, 이 API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있으며, 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 합니다.

출처: https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

MDN에서는 위와 같이 CORS에 대해서 설명하고 있습니다.

위 예시에서 나온 것처럼 https://domain-a.com 에서 JS의 XMLHttpRequest, Fetch API, Axios와 같은 것들로 https://domain-b.com/data.json 에 데이터를 얻기 위해 HTTP 통신을 한다면 이 둘의 출처가 다르기 때문에 교차 출처 HTTP 요청을 실행합니다.

그러나 브라우저는 보안상의 이유로 교차 출처 HTTP 요청을 제한합니다. 따라서 이때 에러가 발생하게 됩니다.
또한 XMLHttpRequest와 Fetch API의 경우 동일 출처 정책을 따라서 자신의 출처와 동일한 리소스만 불러올 수 있습니다.

그렇다면 동일 출처 정책란 무엇일까요?

Same-Origin policy

동일 출처 정책(same-origin policy)은 어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식입니다. 동일 출처 정책은 잠재적으로 해로울 수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄여줍니다.

출처: https://developer.mozilla.org/ko/docs/Web/Security/Same-origin_policy

마찬가지로 MDN을 참고하여 무엇인지 알아보았는데 이를 정리하면 두 URL의 프로토콜, 호스트, 포트(명시한 경우)가 모두 같아야만 동일한 출처라고 보고 두 URL 간의 상호작용이 가능하게 하는 것입니다.

하지만 저의 프로젝트 경험처럼 프론트와 백엔드 서버가 서로 달라 교차 출처 HTTP요청이 필요한 경우가 일반적입니다. 그러면 JavaScript로 개발 시, 이 교차 출처 HTTP요청을 하려면 어떠한 방법이 있을까요?

CORS 에러 해결법

Proxy Server 이용(클라이언트)

프록시 서버는 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해 주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다. 서버와 클라이언트 사이에 중계기로서 대리로 통신을 수행하는 것을 가리켜 '프록시', 그 중계 기능을 하는 것을 프록시 서버라고 부른다.

출처: https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9D%EC%8B%9C_%EC%84%9C%EB%B2%84

관련 예시

위에 정의된 것처럼 프록시 서버를 이용하면 클라이언트 측에서 문제를 해결할 수 있습니다.

JavaScript에서 프론트엔드는 WebPack dev server를 사용하고, express로 만든 백엔드 서버에 연결하여 리소스를 가져온다고 가정해보겠습니다.

프론트엔드의 출처를 https://localhost:8080 이라고 하고 백엔드 서버의 출처를 https://localhost:3000 이라고 가정하였을 시, 현재 이 둘의 출처는 다르기 때문에 프록시 설정을 해주어야 프론트 엔드에서 백엔드 서버에 요청을 할 수 있습니다.

이러한 경우, webpack 에서는 다음과 같이 설정을 해주면 문제를 해결할 수 있습니다.

webpack.config.js

...
proxy: {
  '/todos': {
    target: 'http://localhost:3000/todos',
    pathRewrite: { '^/todos': '' },
  },
},
...

이 코드는 클라이언트가 /todos 요청과 관련한 것들에 대해서는 :3000번 포트를 사용하게 만들어 주어 백엔드 서버에 접근이 가능하도록 만들어줍니다.(Access-Control-Allow-Origin 헤더 추가)
pathRewrite의 경우 경로가 달라져야한다면 rewrite을 설정하는 부분입니다.

Express의 cors middleware 사용(서버)

express로 백엔드 서버를 개발하였다면 cors middleware로 문제를 해결할 수 있습니다.

express 개발환경에서 cors를 설치하여 다음과 같이 사용하면 됩니다.

npm install cors

출처: https://expressjs.com/en/resources/middleware/cors.html

위 예제는 모든 환경에서 cors-origin 요청에 대해 허용을 해주는데 몇몇 출처에 한해서 응답하는 경우 이에 따른 옵션을 줄 수 있습니다.

var express = require('express')
var cors = require('cors')
var app = express()

var 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')
})

마무리

지금까지 Cors와 Same-origin policy 그리고 이 cors 문제를 해결하기 위해 어떠한 방법이 있는 지 알아보았습니다.

proxy를 사용하는 방법과 cors middleware를 사용하는 방법이 있었는데 이 두 가지 방법이 각각 어떠한 장단점을 가지고 있는지 알아보기 위해 구글링을 해보았는데 자세한 자료가 없어서 아쉬웠습니다. 일반적인 프록시에 대한 장단점과 같은 글은 vpn과 비교하는 글들이 많았습니다.

클라이언트측에서 cors 설정과 서버측에서 cors 설정을 하는 경우에 보통 서버측에서 cors 설정을 하는 경우가 많고, 더 낫다고 하는데 그 이유까지 알아보면 좋을 것 같습니다.

참고자료

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글