
프론트엔드 개발자라면 한 번쯤 겪었을 그 문제 CORS Error. 이번 글에서는 CORS(Cross-Origin Resource Sharing)의 개념부터 원리, 해결 전략까지 깔끔히 정리해본다.
1. CORS란?
CORS (Cross-Origin Resource Sharing) 은 브라우저가 보안을 위해 다른 출처(origin)의 리소스 요청을 제한하는 정책이다.
즉, 브라우저는 자신이 실행 중인 웹 페이지와 다른 도메인으로의 요청을 기본적으로 차단한다. 이는 XSS(Cross-Site Scripting) 나 CSRF(Cross-Site Request Forgery) 같은 보안 공격을 방지하기 위한 핵심 메커니즘이다.
2. “출처(Origin)”란 무엇인가?
하나의 Origin(출처) 은 다음 세 가지로 구성된다.
| 구성 요소 | 예시 |
| Scheme (Protocol) | https:// |
| Host (Domain) | www.naver.com |
| Port (Port Number) | :443 |
세 가지가 모두 같아야 동일 출처로 간주한다. 단 하나라도 다르면 교차 출처(Cross-Origin) 요청이 된다.
예시
| 요청 URL | 동일 출처? | 이유 |
| https://example.com/api | ✅ | 완전히 동일 |
| http://example.com/api | ❌ | 프로토콜 다름 |
| https://api.example.com | ❌ | 서브도메인 다름 |
| https://example.com:3000 | ❌ | 포트 다름 |
3. 왜 CORS 에러가 발생할까?
브라우저는 보안을 위해 “다른 출처로의 요청”을 자체적으로 차단한다. 하지만 실제 서비스에서는 프론트엔드와 백엔드가 서로 다른 도메인이나 포트에서 동작하는 경우가 많다.
예를 들어:
// 1. 도메인이 다른 경우
Frontend: https://app.myservice.com
Backend: https://api.myservice.com
// 2. 포트번호가 다른 경우
Frontend: http://localhost:3000
Backend: http://localhost:4000
이 경우 브라우저는 이렇게 판단한다. “이건 다른 Origin이다. 이 요청이 안전한지 모르겠다. 서버에게 CORS 정책을 확인해야겠다.”
그리고 서버가 아래 응답 헤더를 주면 요청이 허용된다.
1. Access-Control-Allow-Origin: https://app.myservice.com
2. Access-Control-Allow-Origin: http://localhost:3000
서버가 이 헤더를 명시하지 않으면 브라우저는 응답을 버리고 콘솔에 CORS Error를 띄운다.
4. 브라우저가 CORS를 처리하는 이유
CORS는 브라우저 단에서만 적용된다. 서버-서버 간 통신(cURL, Postman, Node.js 요청 등)은 이 정책의 영향을 받지 않는다.
왜냐하면, “서버는 신뢰된 환경에서 통신하지만, 브라우저는 사용자의 보안이 걸린 공개된 클라이언트 환경이기 때문이다.”
즉, 브라우저(클라이언트)는 “서버가 이 데이터를 특정 출처(Origin)에 제공해도 된다고 허락했는지" 확인하고, 응답 접근을 차단하거나 허용한다. 예를 들어, 누군가 악성 스크립트를 심은 사이트에 사용자가 접속했을 때, 그 스크립트가 임의로 다른 사이트(예: 은행, 이메일 서버)에 요청을 보내더라도, 브라우저는 그 응답 데이터에 접근하는 것을 차단하여 민감한 정보가 유출되는 것을 방지한다.
5. 요청 흐름 이해하기
- 악성 스크립트를 심은 사이트는 사용자가 방문한 “위험한 클라이언트 페이지”
- 다른 사이트(예: 은행, 이메일 서버) 등은 요청을 받는 서버 (다른 출처 Origin)
- 브라우저는 두 곳 사이에서 요청과 응답을 중계하면서 보안을 감시하는 존재
예시 요청 흐름을 살펴보자.
1. 사용자가 malicious-site.com 에 접속했다. (이건 클라이언트, 즉 React 앱이나 HTML 페이지를 의미)
2. 이 사이트 내부의 악성 스크립트가 https://bank.com/api/accounts 같은 다른 출처의 서버에 요청을 시도한다.
fetch("https://bank.com/api/accounts")
3. 브라우저는 요청을 보내기 전에 생각한다.
"잠깐, 지금 페이지(malicious-site.com)에서 다른 출처(bank.com)로 요청을 보내고 있네? 이건 CORS 정책 위반일 수도 있겠는데?” 그래서 브라우저는 bank.com 서버에 “이 요청 허용할까요?”라는 의미로 Preflight 요청(OPTIONS) 을 먼저 보낸다.
4. 만약 bank.com 이 응답 헤더로
Access-Control-Allow-Origin: https://malicious-site.com
를 보내지 않으면, 브라우저는 이 요청을 차단한다.
위 예시처럼, CORS 요청은 두 가지 방식으로 나뉜다.
| 구분 | 설명 |
| Preflight Request (사전 요청) | OPTIONS 메서드로 미리 서버에 “이 요청을 보내도 괜찮은가?”를 묻는 과정 |
| Simple Request | GET/POST 요청 중, 특정 헤더와 MIME 타입만 포함된 단순 요청 |
이처럼 서버가 사전 요청에 올바르게 응답해야 CORS Error 가 발생하지 않고 실제 API 호출이 이어진다.
# 예시: Preflight 요청 응답 헤더
Access-Control-Allow-Origin: https://secure-site.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
6. 쿠키와 인증 정보가 필요한 경우
교차 출처로 요청 시, 쿠키나 세션 정보를 포함하려면 클라이언트와 서버 모두 별도의 설정이 필요하다.
프론트엔드 (React, Axios 등)
axios.get('https://api.example.com/user', {
withCredentials: true
});
백엔드 (Express.js)
res.setHeader('Access-Control-Allow-Origin', 'https://myapp.com');
res.setHeader('Access-Control-Allow-Credentials', 'true');
이렇게 해야 브라우저가 쿠키를 함께 전송하고 응답도 정상적으로 수신한다.
7. 개발 중 CORS 문제 해결 방법
- 서버 설정 수정 (가장 권장되는 방법)
- 백엔드에서 Access-Control-Allow-Origin 헤더를 추가한다.
- 필요 시 도메인별 화이트리스트를 관리한다.
- *프록시(proxy) 설정 (개발 환경용)
- 로컬 개발 시 동일 출처처럼 동작하도록 중간 서버를 둔다.
- 예: CRA → setupProxy.js, Next.js → rewrites 설정 사용
- 브라우저 플러그인 (비추천)
- 개발 테스트 용도로만 사용한다.
- 보안상 위험하므로 배포 환경에서는 절대 사용하지 않는다.
*프록시 서버
- 브라우저는 CORS 정책 때문에 다른 출처(origin)의 API를 바로 호출할 수 없다.
- 프록시 서버는 브라우저 → 같은 출처 → 프록시 → 외부 API로 요청을 전달한다.
- 브라우저 입장에서는 프록시 서버가 동일 출처이므로 CORS 문제가 발생하지 않는다.
- 장점: 인증, 토큰, 헤더 등 민감 정보를 숨길 수 있고, 개발/운영 환경 모두에 적용 가능하다.
[브라우저] ---> [내 서버 / 프록시] ---> [외부 API 서버]
1.브라우저 요청
- 사용자가 프론트엔드에서 /api/data를 요청한다.
- 브라우저 입장에서는 요청이 동일 출처로 향하기 때문에 CORS 오류가 발생하지 않는다.
React 개발 환경 (CRA)에서 프록시 설정 예제:
// package.json
{
"name": "my-app",
"version": "0.1.0",
"dependencies": { ... },
"proxy": "http://localhost:4000"
}
// React에서 fetch 요청
fetch('/api/data') // 실제로는 localhost:4000/api/data로 전달
.then(res => res.json())
.then(data => console.log(data));
- 개발 환경에서 /api/... 요청은 모두 http://localhost:4000/api/...로 프록시된다.
- 브라우저는 프록시 서버와 동일 출처로 통신한다고 인식하여 CORS 문제 발생 없음.
2. 프록시 서버 처리
- 프록시 서버가 브라우저 요청을 받아 내부적으로 외부 API로 전달한다.
- 필요 시 인증 토큰이나 헤더를 추가해 안전하게 외부 API와 통신한다.
Express 서버에서 프록시 구현 예제:
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
// 프록시 엔드포인트
app.get('/api/data', async (req, res) => {
try {
// 외부 API 호출
const apiRes = await fetch('https://external-api.com/data');
const data = await apiRes.json();
// 브라우저에 그대로 전달
res.json(data);
} catch (err) {
res.status(500).json({ error: 'Failed to fetch data' });
}
});
app.listen(4000, () => console.log('Proxy server running on port 4000'));
- 브라우저에서는 fetch('/api/data')로 요청
- 프록시 서버가 외부 API 호출 후 응답을 브라우저로 전달
- 브라우저는 동일 출처로 요청했으므로 CORS 문제 없음
3.외부 API 응답
- 외부 API가 데이터를 반환한다.
- 프록시 서버는 받은 응답을 브라우저로 그대로 전달한다.
운영 환경 (Nginx) 프록시 예제:
server {
listen 80;
server_name my-frontend.com;
location /api/ {
proxy_pass https://external-api.com/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
root /var/www/html;
index index.html;
}
}
- 브라우저는 my-frontend.com/api/...로 요청
- Nginx 프록시가 외부 API로 요청 전달 → 응답 브라우저로 전달
- 브라우저 입장에서는 동일 출처 응답으로 인식 → CORS 문제 없음
4. 브라우저에서 응답 처리
- 브라우저는 프록시 서버로부터 응답을 받아 처리한다.
- 사용자에게 데이터를 표시하고, 필요 시 UI 업데이트를 진행한다.
정리: 브라우저 → 프록시 서버 → 외부 API → 프록시 서버 → 브라우저 순서로 동작하며, 이 과정을 통해 CORS 문제 없이 외부 API 데이터를 안전하게 가져올 수 있다.
8. 실무 팁
| 상황 | 해결 방향 |
| API 서버가 외부에 있음 | 서버에서 Access-Control-Allow-Origin을 설정한다. |
| 로그인/세션 포함 요청 | withCredentials + Allow-Credentials: true 설정을 사용한다. |
| 로컬 개발 중 테스트 | 프록시 설정으로 동일 출처처럼 구성한다. |
| 특정 도메인만 허용 | 와일드카드(*) 대신 명시적 도메인을 지정한다. |
9. 정리 및 마무리
CORS는 “서버 간 통신이 아닌, 브라우저의 보안 장치”다. CORS는 복잡해 보이지만 결국 서버가 어떤 출처의 요청을 신뢰할지 명시하는 제도다. 보안을 위해 존재하지만, 프론트엔드 개발자에게는 가장 자주 등장하는 실무 이슈 중 하나다.
CORS 에러를 만났다면
- 서버 응답 헤더를 먼저 확인하고,
- 요청이 단순한지(Preflight 여부),
- 쿠키/인증 정보가 필요한지
를 순서대로 점검한다.
“CORS는 버그가 아니라, 브라우저의 방패다.”
'Computer Science > Web Development' 카테고리의 다른 글
| 주소창에 google.com을 입력하면 일어나는 일 (0) | 2025.10.18 |
|---|---|
| 웹 서버(Nginx, Apache..) 의 역할 (0) | 2025.10.18 |
| JWT(Json Web Token) (0) | 2025.10.17 |
| SPA, CSR, SSR, 그리고 Hybrid Rendering — 웹 렌더링의 진화 (0) | 2025.10.17 |
| z-index가 같아도 겹치는 순서가 달라지는 이유 (0) | 2025.10.16 |