📢 어렵고 정석적인 개념 설명보다는 저같은 초보자도 이해하기 쉽게 정리하는 것을 원칙으로 포스팅하고 있습니다. 😄
CORS 오류가 뭔데요?
CORS(Cross-Origin Resource Sharing) 오류는 프론트엔드 개발자라면 흔히 겪는 오류 중 하나로, 브라우저에서 서버로 API를 요청할 때 자주 발생한다. 브라우저는 보안 문제에 매우 민감하기 때문에, 다른 출처(다른 도메인, 프로토콜, 포트)에서 들어오는 요청에 대해 항상 검사를 진행한다. 이를 동일 출처 정책(Same-Origin Policy)이라고 하며, 이 정책은 웹 페이지가 자신이 로드된 출처와 다른 출처로부터 리소스를 요청하는 것을 기본적으로 제한한다.
클라이언트(브라우저)에서 다른 출처로 요청을 보낼 때, 요청을 받은 서버는 응답을 보낼 수 있다. 그러나 응답을 받은 브라우저는 그 응답이 사용해도 되는 것인지 확인하고, 서버가 올바른 CORS 헤더(Access-Control-Allow-Origin)를 포함하지 않았다면 해당 응답을 차단한다. 즉, CORS 오류는 서버가 요청에 응답하지 않아서 발생하는 것이 아니라, 서버의 응답에 브라우저가 요청한 출처와 동일하거나 허용된 출처에서만 접근 가능하다는 것을 명시적으로 표시하는 CORS 헤더가 포함되지 않았기 때문에 발생하는 것이다.
CORS 오류를 해결하려면 서버 측에서 적절한 CORS 헤더를 설정하여 클라이언트가 요청한 출처에서 응답을 할 수 있도록 허용해야 한다. 예를 들어, 서버가 응답 헤더가 'Access-Control-Allow-Origin: *' 또는 특정 도메인을 설정하면 브라우저는 그 응답을 사용할 수 있게 된다.
클라이언트-서버 간 HTTP 요청과 응답 과정
클라이언트가 서버에게 POST, PUT 등의 HTTP 메서드를 이용해서 데이터를 요청한다고 가정해 보자. 서로 다른 출처로의 요청 중 일부는 '사전 검사(Preflight)'라는 과정을 거친다. 이 과정에서 브라우저는 OPTIONS 메서드를 사용해 서버가 특정 HTTP 메서드와 헤더를 사용하는지를 확인한다. 이때 요청에는 실제 데이터가 아닌, 요청하려는 메서드 및 헤더 정보가 전송된다.
// OPTIONS 요청 (사전 검사)
OPTIONS /api/resource HTTP/1.1
Host: example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
// POST 요청 (실제 데이터 전송)
POST /api/resource HTTP/1.1
Host: example.com
Content-Type: application/json
Authorization: Bearer your_access_token
{"key": "value"}
위 코드는 모두 클라이언트가 서버로 요청할 때 전송되는 데이터이다. OPTIONS 요청을 통해 브라우저는 서버가 특정 HTTP 메서드와 헤더를 허용하는지를 확인하고, 서버가 이를 허용하는 경우에만 브라우저가 실제 데이터를 포함한 POST 요청을 서버로 전송한다. (브라우저가 서버에게 "나 너한테 이렇게 보낼건데 괜찮아?" 라고 물어보는 과정이다.)
또한, 사전 검사 후에도 브라우저는 실제 데이터 요청 단계에서 서버의 응답 헤더에 포함된 'Access-Control-Allow-Origin'을 확인하여 CORS 정책을 적용한다. 만약 서버가 이 헤더를 포함하지 않거나 현재 출처와 일치하지 않다면, 브라우저는 CORS 오류를 발생시켜 요청을 차단한다.
사전 검사는 일반적으로 'Content-Type'이 특정 타입이 아니거나 사용자 정의 헤더가 포함된 경우, 또는 자격 증명 정보가 포함된 요청을 할 때 발생한다. 이는 CORS 정책을 적용하여 보안을 강화하기 위해 도입된 절차이다. (GET 요청의 경우에도 특정 조건을 만족하면 사전 검사가 발생할 수 있다.)
CORS의 두 가지 검증 단계
'Access-Control-Allow-Origin' 확인은 사전 검사 단계와 실제 요청 단계에서 모두 이루어진다. 이 두 단계에서 'Access-Control-Allow-Origin'을 확인하는 이유와 방법에는 차이가 있는데, 이것을 잠깐 정리하고자 한다.
사전 검사(Preflight) 요청 단계에서의 검증은 ① 서버가 특정 출처에서의 요청을 허용하는지(Access-Control-Allow-Origin) ② 서버가 특정 HTTP 메서드(POST, PUT, DELETE)를 허용하는지 ③ 서버가 특정 요청 헤더(Content-Type, Authorization)을 허용하는지를 확인한다. 서버가 사전 검사 요청에 대해 올바른 응답을 반환하면, 브라우저는 해당 요청이 허용된 것으로 판단하고 실제 요청을 수행한다.
사전 검사가 성공하면, 브라우저는 실제 데이터가 포함된 요청을 서버에 보낸다. 서버는 이 요청을 처리하고 브라우저에게 응답을 보내는데 서버의 응답에는 'Access-Control-Allow-Origin' 헤더가 포함되어 있어야 한다. 브라우저는 이 응답을 다시 한 번 검토하여, 응답이 요청한 출처(Origin)에서 온 것인지 확인한다. 서버의 응답 헤더에 있는 'Access-Control-Allow-Origin'이 실제 요청의 출처와 일치하거나, '*'로 모든 출처를 허용하는 경우에만 브라우저는 응답을 수락한다. 만약 응답의 'Access-Control-Allow-Origin' 헤더가 부적절하거나 누락되면, 브라우저는 해당 응답을 차단하고 CORS 오류를 발생시킨다.
'Access-Control-Allow-Origin'을 두 번 확인하는 이유
사전 검사 단계는 보안상의 이유로 "이 출처에서의 요청을 허용하는가?"라는 근본적인 허용 여부를 미리 확인하는 것이다. 즉, 데이터가 전송되기 전에 불필요한 요청을 방지하기 위해 사용된다.
실제 요청 단계에서의 확인은 "실제 데이터가 포함된 응답을 허용된 출처로부터 받았는가?"라는 보안 검증을 추가로 수행한다. 이는 중간에 출처가 바뀌거나, 응답이 올바르지 않게 구성된 경우를 방지하기 위함이다.
CORS 오류를 해결하는 방법 (프록시 서버)
Access-Control-Allow-Origin: * // 모든 접근 허용
요청을 받은 서버의 헤더에 'Access-Control-Allow-Origin'이 '*'로 되어 있었다면 CORS 오류가 발생하지 않았을 것이다. 하지만, 보안 상의 이유로 대부분의 서버는 '*'로 설정되어 있지 않다. CORS 오류를 해결하는 방법은 'Access-Control-Allow-Origin' 값에 요청한 클라이언트의 출처(Origin)을 넣어주면 간단히 해결된다. 하지만, 문제는 우리가 저 서버를 직접 변경할 수 없는 경우에 발생한다.
응답 서버를 수정할 수 없다면, 클라이언트 → 응답 서버 대신 중간에 프록시 서버를 두는 방법을 사용할 수 있다.
즉, 클라이언트 → 프록시 서버 → 응답 서버의 과정을 거치는 것이다. CORS 오류는 브라우저에서 발생하는 오류로, 서버 간의 통신에서는 동일 출처 정책이 적용되지 않기 때문에 발생하지 않는다. 따라서, 서버에서 서버로 데이터를 요청할 때는 출처가 달라도 CORS 오류가 발생하지 않는다는 점을 이용할 수 있는 것이다.
🧐 클라이언트 → 프록시 서버 → API 서버 과정
1) 클라이언트는 프록시 서버로 API 요청을 보낸다.
2) 프록시 서버는 클라이언트로부터 받은 요청을 API 서버에 전달한다.
3) API 서버는 요청을 처리하고 응답을 프록시 서버로 전송한다.
4) 프록시 서버는 받은 응답을 클리어언트에게 전달한다.
이런 방식으로 프록시 서버가 클라이언트와 API 서버 사이에 위치함으로써, 클라이언트는 실제 API 서버와 직접 통신하지 않고 프록시 서버를 통해 간접적으로 통신할 수 있다. 즉, 이를 통해 CORS 정책을 회피할 수 있다.
그러나, 프록시 서버를 설정하기 위해서는 서버 환경을 관리할 수 있는 백엔드 개발자의 도움이 필요할 수 있다. CORS 오류는 서버의 CORS 헤더 설정 때문에 발생하는 문제이지만, 서버 설정을 변경하지 않고도 프록시 서버를 사용하는 등의 방법으로 해결할 수 있다.
1. CORS 오류는 브라우저와 서버 간의 요청이 서로 다른 출처에서 발생할 때, 브라우저가 보안을 위해 요청을 거부하면서 발생한다. (CORS 오류는 브라우저 쪽에서 나타나는 오류)
2. 브라우저는 민감한 요청(POST, PUT, DELETE 등)을 하기 전에 사전 검사(Preflight)를 수행하고, 이때 OPTIONS 메서드를 사용해 서버가 특정 요청을 허용하는지 확인한다.
3. 실제 요청을 할 때에도 브라우저는 서버의 응답 헤더에 포함된 'Access-Control-Allow-Origin'을 확인하여 요청을 허용할지 결정한다.
4. CORS 오류는 클라이언트와 서버 사이에 프록시 서버를 두어 우회할 수 있다. 서버 간의 통신에는 CORS 정책이 적용되지 않기 때문이다.
※ 한 줄 정리 : "서버 응답 쪽에 'Access-Control-Allow-Origin'이 허용되지 않으면 브라우저에서 CORS 오류 발생"