웹 개발을 하다 보면 "CORS 에러가 발생했다"는 메시지를 마주하게 된다. 이 문제를 해결하기 위해 많은 개발자들이 프록시(Proxy)를 사용한다. CORS가 무엇인지, 왜 발생하는지, 그리고 프록시를 통해 어떻게 해결하는지 알아보자.
CORS는 "교차 출처 리소스 공유"라는 의미이다. 간단히 말해, 한 출처(Origin)에서 다른 출처의 리소스에 접근할 때 적용되는 보안 정책이다.
출처는 프로토콜, 도메인, 포트 세 가지 조합으로 정의된다.
https://example.com:3000
│ │ │
프로토콜 도메인 포트
예를 들어, 다음은 모두 다른 출처이다.
https://example.com (포트 없음)https://example.com:3000 (포트 3000)http://example.com (프로토콜이 http)https://api.example.com (서브도메인이 다름)브라우저는 보안상의 이유로 같은 출처 정책(Same-Origin Policy)을 강제한다. 예를 들어:
프론트엔드: https://myapp.com
API 서버: https://api.example.com
→ CORS 에러 발생!
프론트엔드에서 다른 출처의 API로 요청을 보내면 브라우저가 이를 차단한다. 이는 악의적인 웹사이트가 사용자의 데이터에 접근하는 것을 방지하기 위함이다.
프록시는 "중개자" 또는 "대리자"라는 의미이다. 웹에서 프록시는 클라이언트와 서버 사이에 위치하여 요청과 응답을 중개한다.
┌─────────────────────────────────────┐
│ 브라우저 (클라이언트) │
│ 출처: https://myapp.com │
└──────────────────┬──────────────────┘
│
↓ (같은 출처로 요청)
┌──────────────────────────────────────┐
│ 프록시 서버 │
│ 출처: https://myapp.com:3000 │
│ (같은 도메인, 다른 포트) │
└──────────────────┬───────────────────┘
│
↓ (서로 다른 출처로 요청)
┌──────────────────────────────────────┐
│ 실제 API 서버 │
│ 출처: https://api.example.com │
└──────────────────────────────────────┘
프록시 서버가 브라우저와 같은 출처에 있으면, 브라우저 입장에서는 같은 출처로 요청하는 것이다. 따라서 CORS 에러가 발생하지 않는다.
Create React App 프로젝트라면 package.json에 한 줄만 추가하면 된다.
{
"proxy": "https://api.example.com"
}
이제 프론트엔드에서 /api로 요청하면 자동으로
https://api.example.com/api로 포워딩된다.
// 브라우저에서
fetch('/api/users')
.then(res => res.json())
.then(data => console.log(data))
더 복잡한 설정이 필요하면 http-proxy-middleware를 사용할 수 있다. src/setupProxy.js파일을 생성한다.
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'https://api.example.com',
changeOrigin: true,
pathRewrite: {
'^/api': '' // '/api/users' → '/users'로 변환
}
})
);
};
Vite를 사용한다면 vite.config.js에서
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
실제 프로젝트에서는 개발, 테스트, 운영 환경마다 다른 API 서버를 가리킬 수 있다.
const API_BASE_URL = process.env.NODE_ENV === 'development'
? 'http://localhost:3001'
: 'https://api.example.com';
const response = await fetch(`${API_BASE_URL}/users`);
서버에서 적절한 CORS 헤더를 설정하면 브라우저가 요청을 허용한다.
// Node.js/Express 예제
const express = require('express');
const cors = require('cors');
app.use(cors({
origin: 'https://myapp.com',
credentials: true
}));
오래된 방식이지만 여전히 사용되기도 한다. 하지만 보안 측면에서 권장되지 않는다.
프론트엔드 프록시 대신 백엔드에서 외부 API를 호출하고, 프론트에서는 백엔드만 호출한다.
프론트엔드 → 같은 서버의 백엔드 → 외부 API
이 방식이 가장 안전하고 권장된다.
| 상황 | 추천 방법 | 이유 |
|---|---|---|
| 개발 단계, 외부 API 테스트 | 프록시 | 빠르고 간단 |
| 본인 서버의 API | CORS 헤더 설정 | 원본 해결, 더 안전 |
| 제3의 외부 API | 백엔드 프록시 | 보안과 유연성 |
| 프로덕션 환경 | 백엔드 프록시 | 가장 안전 |
프록시는 개발 환경에서만 작동한다. 프로덕션에 배포되면 프록시가 작동하지 않으므로 반드시 다른 해결책이 필요하다.
프록시를 통해 민감한 데이터(API키 등)를 숨길 수 있지만, 프론트엔드 코드는 여전히 노출된다. 중요한 인증 정보는 반드시 백엔드에서 관리해야 한다.
// ❌ 위험: API 키가 프론트엔드에 노출
const API_KEY = 'sk_live_xxxx';
// ✅ 안전: 백엔드를 통해 관리
fetch('/api/internal/users'); // 백엔드가 API 키를 사용
CORS는 웹의 보안을 지키는 중요한 메커니즘이다. 프록시는 개발 단계에서 편리한 도구이지만, 완전한 해결책은 아니다. 상황에 맞게 프록시, CORS 헤더 설정, 백엔드 프록시를 적절히 조합하여 사용하면 안전하고 효율적인 웹 애플리케이션을 만들 수 있다.
개발할 때는 프록시로 빠르게 테스트하고, 프로덕션 배포 전에는 반드시 CORS 설정이나 백엔드 프록시로 전환하는 것을 잊지 말자.