착하고 좋은 친구 CORS

차차·2024년 2월 20일
5
post-thumbnail

⚠️ 어디서 많이 본 메시지

Access to fetch at 'https://server-api.com' 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


어디서 많이 본 에러 메시지이다. 해석하자면,

"http://localhost:3000" 출처에서 "https://server-api.com" 리소스에 대한 액세스가 CORS 정책에 의해 차단되었습니다: 요청된 리소스에 'Access-Control-Allow-Origin' 헤더가 없습니다. 불투명한 응답이 필요한 경우, 요청 모드를 'no-cors' 로 설정하여 CORS를 비활성화한 상태로 리소스를 가져올 수 있습니다.


CORS 란?

Cross Origin Resource Sharing, 교차 출처 리소스 공유

만약 서버가 제한없이 모든 사이트에서의 요청을 열어두게 된다면, 개인 정보 탈취와 같은 보안 문제가 마구 발생할 것이다. 따라서 브라우저는 기본적으로 서로 다른 출처(Cross-Origin)에서의 HTTP 요청을 제한한다.

하지만 이렇게 서로 다른 출처를 아예 차단해버리는 SOP(Same-Origin Policy, 동일 출처 정책)를 곧이 곧대로 하면 상당히 불편하다. https://server.com 은 무조건 https://server.com 에서만 요청을 받을 수 있다.

따라서 출처가 다른 요청도 가능하게 하면서, 보안상의 문제도 해결하기 위한 메커니즘이 CORS 이다.

서버는 신뢰가능한 출처일 경우에만 HTTP 요청을 허용하고, 그 외의 출처는 요청을 차단할 수 있다. 그게 바로 CORS 설정!

브라우저에서 서버로 요청을 날릴 때, 동일한 출처가 아니거나 등록된 출처가 아니라면 발생하는 것이 위와 같은 CORS 위반 에러이다.


교차 출처

URL 의 프로토콜(스킴), 호스트명, 포트번호가 모두 같아야 동일한 출처로 본다. 하나라도 다르면 교차 출처로 여겨진다.

서버의 주소가 https://server.com 이라면,

URL같은 출처?
1http://server.comX
2https://server.com/docs?id=123O
3https://server.com:3000X
4http://localhost:5173X

따라서 CORS 설정으로 해당 URL 을 따로 허용한 경우가 아니라면, 2번 클라이언트를 제외하고는 모두 CORS 위반 에러가 발생할 것이다.


CORS 작동

브라우저가 CORS 위반 여부를 검사하는 방법

허용되지 않은 곳에서 부터 요청이 날아오고, 그걸 서버가 차단하는 것 처럼 보이기 때문에 서버에서 에러를 낸다고 생각이 될 수도 있다.

하지만 그렇지 않다!
우리가 보는 CORS 에러는 브라우저에서 발생한다.
서버는 아무것도 모른다.

브라우저가 CORS 에러를 통보 하기까지의 과정은 아래와 같다.

  1. 브라우저에서 서버로 요청을 보낸다.
    • 브라우저는 요청 헤더에서 Origin 이라는 필드에 출처를 같이 보낸다.
      • 서버에서 CORS 정책을 적용해서 아예 요청을 거부할 수도 있는데, 이러한 경우에 쓰인다.
  1. 서버에서 응답을 내려준다.
    • 서버는 Access-Control-Allow-Origin 에 허용된 출처들을 내려준다.
  1. 브라우저에서 본인의 출처(Origin)와 서버에서 받은 허용 출처(Access-Control-Allow-Origin)를 비교한 후, 허용되지 않았다면 CORS 에러를 발생시킨다.

즉, 서버에서 아예 요청을 거부하지 않는 이상 정상적으로 응답은 받을 수 있다. 하지만 브라우저가 CORS 위반 경고를 띄워주는 것이다. 따라서 CORS 에러가 나타나도 HTTP 응답코드는 200 이었던 것이다.

대부분 사전 요청 방식(preflight)으로 리소스 공유가 이루어지고, 실제 요청이 이루어지기 전 CORS 위반에러를 만날 수 있다.

❓ Preflight

실제 요청을 보내기 전 브라우저가 서버에게 사전 요청을 보내고, 서버가 이에 대한 응답을 내려주는 방식이다. 예비 요청을 보냄으로써, 브라우저가 이후의 본 요청들을 보낼 수 있는 지 확인하고 안전한 요청인지 판단할 수 있다.

  1. 브라우저에서 서버로 예비 요청 전송
  2. 서버는 어떤 것을 허용하고 금지하는 지 (허용된 출처들 포함) 헤더에 담아서 응답
  3. 브라우저에서는 안전하다 판단되면 동일 출처로 실제 요청 전송
    • 브라우저, 서버 HTTP 헤더에 담긴 Access-Control-Allow-* 비교

CORS 에러 해결

서버에서 설정해주기 (best!)

서버 CORS 설정 코드, localhost 에서의 요청을 허용했다

가장 근본적이고 쉬운 해결 방법은, 이렇게 서버에서 cross-origin 허용 URL 을 추가하는 것이다.

클라이언트에서 프록시 설정 해주기

클라이언트→서버 사이에 중계자를 둬서, 동일한 origin 인 척하는 방법이다.

  • 로컬 개발 환경(localhost)이라면, 임시적으로 프록시 설정을 통해 해결할 수 있다.
  • 프로덕트라면, 아마 실제 프록시 서버를 구축하거나 빌려와야 할 것이다.

vite 환경에서 프록시 설정해주기 ⬇️

// vite.config.js

export default defineConfig({
  server: {
    proxy: {
      // http://localhost:5173/foo -> http://localhost:4567/foo
      '/foo': 'http://localhost:4567',
      // http://localhost:5173/api/bar-> http://jsonplaceholder.typicode.com/bar
      '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        secure: false,
        rewrite: (path) => path.replace(/^\/api/, '')
      },
      // http://localhost:5173/fallback/ -> http://jsonplaceholder.typicode.com/
      '^/fallback/.*': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/fallback/, '')
      },
    }
  }
})

cra 환경에서 프록시 설정해주기 (http-proxy-middleware 라이브러리 사용) ⬇️

// src/setupProxy.js
    
import { createProxyMiddleware } from 'http-proxy-middleware';

module.exports = (app) => {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'https://server.com',
      changeOrigin: true,
    })
  )
}
    

결론

CORS 는 무서워할 필요 없다. 유연하지만 안전하게 해주는 ✨착한 친구✨이기 때문이다. 우리가 만나는 에러는 엄밀히 말하면 CORS 위반 에러이다. 브라우저가 자체 발생시키는 에러이다.

0개의 댓글