Vue나 React로 SPA 화면 개발시 UI에서 다른 서버의 API를 바로 요청하면 아래와 같이 CORS 에러를 만나게 된다.
Access to fetch at ‘http://localhost:8000’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
CORS는 Cross-Origin Resource Sharing의 줄임말로 W3C에서 내놓은 정책이라고 한다. 이는 특정 헤더를 통해서 Cross-Origin, 즉 교차 된 출처(Origin)에서 리소스를 공유되는 것이 허용된 것인지를 알 수 있는 방법이다.
Origin, 출처
URL 을 나타내는 출처는 서버의 위치를 찾아가기 위해 필요한 가장 기본적인 것들을 합쳐놓은 것이고 다음과 같이 표현될 수 있다고 할 수 있다.
사실 두 개의 출처가 서로 같다고 판단하는 방법은 두 URL의 구성 요소 중 Protocol, Host, Port, 이 3가지만 동일하면 된다.
CORS는 브라우저의 구현 스펙에 포함되는 정책이기 때문에, 브라우저를 통하지 않고 서버 간 통신을 할 때는 이 정책이 적용되지 않는다.
출처를 비교를 하는 것은 각 브라우저에서 아래의 순서로 진행된다.
- CORS 정책을 위반하는 리소스 요청
- 해당 서버가 같은 출처에서 보낸 요청만 받겠다는 로직을 가지고 있는 경우가 아니라면 서버는 정상적으로 응답한다.
- 브라우저가 CORS 정책 위반이라고 판단되면 그 응답을 사용하지 않는다.
CORS 의 동작
- 기본적으로 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜을 사용하여 요청을 보내게 된다.
이때 브라우저는 요청 헤더에 Origin이라는 필드에 요청을 보내는 출처를 함께 담아보낸다 - 서버가 응답 헤더의 Access-Control-Allow-Origin 값에 “허용된 출처” 값을 설정해 응답한다.
- 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정한다.
CORS가 동작하는 방식은 자세히 들여다보면 한 가지가 아니라 세 가지의 시나리오에 따라 변경된다고 한다.
Preflight Request
이 방식을 사용하는 브라우저는 요청을 한번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송한다.
- HTTP 메소드 중 OPTIONS 메소드가 사용된다.
예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인하는 것이다.
Simple Request
이 시나리오에 대한 정식 명칭은 없지만 MDN의 CORS 문서에서는 이 시나리오를 Simple Request라고 부르고 있다.
이 방식은 요청을 전송한 뒤 받은 응답에서 Access-Control-Allow-Origin과 같은 값을 보내주면 그때 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다.
이 방식은 아래와 같은 조건이 성립되어야 사용할 수 있다고 한다!
- 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
- 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만 허용된다.
Credentialed Request
이 방법은 CORS의 기본적인 방식이라기 보다는 다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법이다.
기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다.
이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 credentials 옵션이다.
요청에 인증 정보가 담겨있는 상태에서 다른 출처의 리소스를 요청하게 되면 브라우저는 CORS 정책 위반 여부를 검사하는 룰에 다음 두 가지를 추가하게 된다.
- Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 명시적인 URL이어야한다.
- 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야한다.
Webpack dev server proxy로 우회 방법
현재 Frontend 는 localhost:3000으로 동작하고 있고 https://www.youtube.com/watch?v=fJ9rUzIMcZQ의 의 결과를 받아보고자 한다고 했을때 cors동작을 살펴보자
아래는 proxy설정없이 그대로 브라우저에서 요청을 해본 결과이다.
api 호출은 fail이 되고 브라우저에 CORS 위반이라고 에러가 출력 된다.
이번에는 아래와 같이 리버스 proxy를 설정하고 해보자. 호출 url은 http:localhost:3000/watch 로 하되 webpack devserver proxy 설정에서 /watch 요청일 경우 https://www.youtube.com으로 로 redirect 되게 설정한다.
build/webpack.dev.conf.js
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
// proxy 설정은 여기서 해준다.
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
}
},
config/index.js
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/watch': {
target: 'https://www.youtube.com',
changeOrigin: true,
//pathRewrite: { '^/api': '' },
}
},
호출시에 내부적으로 url을 proxy 설정대로 바꾸어서 호출하므로 브라우저를 우회해서 CORS 위반을 회피하여 정상적으로 응답을 가져왔다.
'Frontend Development > NodeJs' 카테고리의 다른 글
[Node.js] NVM으로 Node.js 버전 여러개 사용하기 (Windows) (0) | 2023.01.14 |
---|