본문으로 건너뛰기

🌐 CORS란?

📖 정의

CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)는 다른 출처(origin)의 리소스에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 보안 메커니즘입니다. 웹 브라우저는 기본적으로 보안상의 이유로 다른 출처의 리소스 요청을 제한하는데, CORS는 이를 안전하게 허용하는 방법입니다.

🎯 비유로 이해하기

아파트 보안 시스템

상상해보세요. 당신이 A동 아파트에 살고 있습니다:

  • 같은 출처(Same-Origin): A동 101호에서 A동 관리실에 전화 → 자유롭게 통화 가능 ✅
  • 다른 출처(Cross-Origin): A동 101호에서 B동 관리실에 전화 → 보안 확인 필요 🔒
  • CORS 설정: B동이 "A동 주민도 전화 받겠다"고 미리 허락 → 통화 가능 ✅

CORS는 이렇게 다른 건물(출처)의 자원에 접근할 때 필요한 허가증입니다!

⚙️ 작동 원리

1. 출처(Origin)란?

출처는 프로토콜, 도메인, 포트의 조합입니다:

https://www.example.com:443/page
│ │ │ │ │
│ │ │ │ └─ 경로 (출처와 무관)
│ │ │ └────── 포트 (생략 시 443)
│ │ └────────────────── 도메인
│ └────────────────────── 프로토콜
└───────────────────────────── 출처 (Origin)

2. 같은 출처 vs 다른 출처

// 현재 페이지: https://www.example.com

✅ 같은 출처 (Same-Origin)
- https://www.example.com/page
- https://www.example.com/api/users

❌ 다른 출처 (Cross-Origin)
- http://www.example.com // 프로토콜 다름
- https://api.example.com // 도메인 다름
- https://www.example.com:8080 // 포트 다름

3. CORS 요청 과정

Simple Request (단순 요청)

1. 브라우저 → 서버: 요청 전송
GET https://api.example.com/data
Origin: https://www.mysite.com

2. 서버 → 브라우저: 응답
Access-Control-Allow-Origin: https://www.mysite.com

3. 브라우저: 응답 확인
- 허용된 출처면 → 데이터 전달 ✅
- 허용 안 되면 → CORS 에러 ❌

Preflight Request (사전 요청)

1. 브라우저 → 서버: 사전 확인 (OPTIONS)
OPTIONS https://api.example.com/data
Origin: https://www.mysite.com
Access-Control-Request-Method: POST

2. 서버 → 브라우저: 허가 응답
Access-Control-Allow-Origin: https://www.mysite.com
Access-Control-Allow-Methods: POST, GET
Access-Control-Max-Age: 3600

3. 브라우저 → 서버: 실제 요청 (POST)
POST https://api.example.com/data

4. 서버 → 브라우저: 데이터 응답

💡 실제 예시

CORS 에러 발생

// 프론트엔드 (https://www.mysite.com)
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));

// ❌ 콘솔 에러
// Access to fetch at 'https://api.example.com/users' from origin
// 'https://www.mysite.com' has been blocked by CORS policy:
// No 'Access-Control-Allow-Origin' header is present on the
// requested resource.

서버에서 CORS 허용하기

Express.js (Node.js)

const express = require('express');
const cors = require('cors');
const app = express();

// 방법 1: 모든 출처 허용 (개발 환경)
app.use(cors());

// 방법 2: 특정 출처만 허용 (운영 환경 권장)
app.use(cors({
origin: 'https://www.mysite.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // 쿠키 포함
maxAge: 3600 // Preflight 캐시 시간 (초)
}));

// 방법 3: 여러 출처 허용
const allowedOrigins = [
'https://www.mysite.com',
'https://admin.mysite.com'
];

app.use(cors({
origin: function(origin, callback) {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));

// API 엔드포인트
app.get('/api/users', (req, res) => {
res.json({ users: ['Alice', 'Bob'] });
});

app.listen(3000);

수동으로 헤더 설정

app.use((req, res, next) => {
// 출처 허용
res.header('Access-Control-Allow-Origin', 'https://www.mysite.com');

// 메서드 허용
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');

// 헤더 허용
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');

// 인증 정보 허용
res.header('Access-Control-Allow-Credentials', 'true');

// Preflight 요청 처리
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}

next();
});

Python Flask

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

# 모든 출처 허용
CORS(app)

# 특정 출처만 허용
CORS(app, resources={
r"/api/*": {
"origins": ["https://www.mysite.com"],
"methods": ["GET", "POST"],
"allow_headers": ["Content-Type"]
}
})

@app.route('/api/users')
def get_users():
return {'users': ['Alice', 'Bob']}

Nginx 설정

server {
listen 80;
server_name api.example.com;

location /api {
# CORS 헤더 추가
add_header 'Access-Control-Allow-Origin' 'https://www.mysite.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;

# Preflight 요청 처리
if ($request_method = 'OPTIONS') {
return 204;
}

proxy_pass http://backend;
}
}

🤔 자주 묻는 질문

Q1. CORS는 왜 필요한가요?

A: 보안을 위해서입니다. CORS가 없다면:

// 악의적인 사이트 (evil.com)
fetch('https://bank.com/api/transfer', {
method: 'POST',
credentials: 'include', // 사용자의 은행 쿠키 포함
body: JSON.stringify({
to: 'hacker-account',
amount: 1000000
})
});

// CORS 덕분에 이런 공격이 차단됩니다!

Q2. CORS 에러 해결 방법은?

A: 상황별 해결 방법:

// 1. 서버 제어 가능 → 서버에서 CORS 헤더 추가 (권장)
app.use(cors({ origin: 'https://frontend.com' }));

// 2. 서버 제어 불가능 → 프록시 서버 사용
// package.json (Create React App)
{
"proxy": "https://api.example.com"
}

// 3. 개발 환경 → 프록시 미들웨어
// setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');

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

// 4. 브라우저 확장 프로그램 (개발 전용!)
// "CORS Unblock" 등의 확장 프로그램 사용
// ⚠️ 운영 환경에서는 절대 안 됨!

Q3. credentials: 'include'는 언제 사용하나요?

A: 쿠키나 인증 정보를 함께 보낼 때 사용합니다:

// 프론트엔드
fetch('https://api.example.com/profile', {
credentials: 'include' // 쿠키 포함
});

// 백엔드 - 반드시 명시적으로 출처 지정
app.use(cors({
origin: 'https://www.mysite.com', // '*' 안 됨!
credentials: true
}));

// ❌ 잘못된 예
app.use(cors({
origin: '*', // 와일드카드
credentials: true // credentials와 함께 사용 불가!
}));

Q4. Simple Request vs Preflight Request 차이는?

A: 조건에 따라 달라집니다:

// ✅ Simple Request (바로 요청)
// - 메서드: GET, HEAD, POST
// - 헤더: Accept, Content-Type 등 기본 헤더만
// - Content-Type: text/plain, multipart/form-data,
// application/x-www-form-urlencoded

fetch('https://api.example.com/data', {
method: 'GET'
});

// ⚠️ Preflight Request (사전 확인 후 요청)
// - 메서드: PUT, DELETE, PATCH
// - 커스텀 헤더: Authorization 등
// - Content-Type: application/json

fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // Preflight 필요
'Authorization': 'Bearer token' // Preflight 필요
},
body: JSON.stringify({ name: 'Alice' })
});

Q5. CORS와 CSRF의 차이는?

A: 완전히 다른 보안 개념입니다:

CORS (Cross-Origin Resource Sharing)
├─ 목적: 다른 출처 자원 접근 허용
├─ 작동: 브라우저가 응답 차단/허용
└─ 해결: 서버에서 허용 헤더 추가

CSRF (Cross-Site Request Forgery)
├─ 목적: 위조된 요청 방지
├─ 작동: 악의적 사이트에서 요청 전송
└─ 해결: CSRF 토큰 사용

🎓 다음 단계

CORS를 이해했다면, 다음을 학습해보세요:

  1. HTTPS란? (문서 작성 예정) - 또 다른 중요한 웹 보안
  2. JWT 토큰 (문서 작성 예정) - API 인증 방법
  3. API란? - API 기초 개념

디버깅 도구

// 브라우저 개발자 도구에서 확인
// Network 탭 → 요청 선택 → Headers 탭

// Request Headers
Origin: https://www.mysite.com
Access-Control-Request-Method: POST

// Response Headers
Access-Control-Allow-Origin: https://www.mysite.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Max-Age: 3600

🎬 마무리

CORS는 웹 보안의 핵심 개념입니다:

  • 출처: 프로토콜 + 도메인 + 포트의 조합
  • Same-Origin Policy: 기본적으로 다른 출처 차단
  • CORS: 안전하게 다른 출처 허용
  • Preflight: 복잡한 요청 전 사전 확인

CORS 에러가 발생하면 당황하지 말고, 서버에서 적절한 헤더를 설정하여 안전하게 해결하세요! 🌐✨