CORS의 정의와 허용 방법
프론트엔드에서 API를 호출하다 보면 브라우저 콘솔에서 CORS 오류를 만날 때가 있습니다. CORS가 무엇인지 이해하려면 먼저 출처(origin)와 동일 출처 정책을 알아야 합니다. CORS의 동작 원리와 대표적인 허용 방법을 정리해 보겠습니다.
Origin이란?
Origin(출처)은 URL의 프로토콜(scheme), 호스트(host), 포트(port)를 합친 값입니다. 세 요소 중 하나라도 다르면 서로 다른 출처로 판단합니다.
| URL | https://example.com과 같은 출처인가? |
이유 |
|---|---|---|
https://example.com/posts |
O | 프로토콜, 호스트, 포트가 같음 |
http://example.com |
X | 프로토콜이 다름 |
https://api.example.com |
X | 호스트가 다름 |
https://example.com:8080 |
X | 포트가 다름 |
URL의 경로(path)는 출처를 구분하는 기준에 포함되지 않습니다.
CORS란?
CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)는 서버가 HTTP 헤더를 이용해 다른 출처에서 실행 중인 웹 애플리케이션에 리소스 접근 권한을 부여하도록 브라우저에 알려 주는 체제입니다.
브라우저는 보안을 위해 스크립트에서 시작한 교차 출처 HTTP 요청을 동일 출처 정책(Same-Origin Policy)으로 제한합니다. 예를 들어 https://frontend.example에서 실행 중인 JavaScript가 https://api.example의 API를 호출하면 두 호스트가 다르므로 교차 출처 요청이 됩니다.
이때 API 서버가 응답에 적절한 CORS 헤더를 포함하면 브라우저는 프론트엔드 코드가 응답을 읽을 수 있도록 허용합니다.
CORS는 서버로 요청이 전송되는 것 자체를 막는 인증·인가 기능이 아닙니다. 브라우저가 응답을 JavaScript에 공개할지 결정하는 규칙이므로 API 보호를 위해서는 별도의 인증과 권한 검사가 필요합니다.
CORS의 동작 방식
단순 요청
일정 조건을 만족하는 GET, HEAD, POST 요청은 별도의 사전 확인 없이 서버로 전송됩니다. 서버가 다음과 같은 응답 헤더를 반환하면 브라우저가 응답 접근을 허용합니다.
Access-Control-Allow-Origin: https://frontend.example
모든 출처에 공개해도 되는 리소스라면 와일드카드를 사용할 수 있습니다.
Access-Control-Allow-Origin: *
하지만 쿠키나 HTTP 인증 정보가 포함된 요청에서는 *를 사용할 수 없으며, 허용할 출처를 명시해야 합니다.
프리플라이트 요청
단순 요청의 조건을 벗어난 교차 출처 요청에는 브라우저가 본 요청 전에 프리플라이트(preflight) 요청을 보낼 수 있습니다. 브라우저는 OPTIONS 메서드로 실제 요청의 메서드와 헤더가 허용되는지 서버에 먼저 확인합니다.
OPTIONS /users HTTP/1.1
Origin: https://frontend.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
서버는 허용할 출처, 메서드, 헤더를 응답에 포함합니다.
Access-Control-Allow-Origin: https://frontend.example
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
브라우저는 프리플라이트 응답에서 요청이 허용되었음을 확인한 뒤 실제 요청을 전송합니다.
CORS 허용 방법
1. 응답 헤더 설정
가장 기본적인 해결 방법은 API 서버가 CORS 응답 헤더를 직접 설정하는 것입니다.
Access-Control-Allow-Origin: https://frontend.example
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
주요 응답 헤더의 역할은 다음과 같습니다.
| 헤더 | 역할 |
|---|---|
Access-Control-Allow-Origin |
응답을 읽을 수 있는 출처 지정 |
Access-Control-Allow-Methods |
허용할 HTTP 메서드 지정 |
Access-Control-Allow-Headers |
실제 요청에서 사용할 수 있는 헤더 지정 |
Access-Control-Allow-Credentials |
쿠키 등 자격 증명이 포함된 요청 허용 여부 지정 |
Access-Control-Max-Age |
프리플라이트 결과를 캐시할 시간 지정 |
보안을 위해 *를 무조건 사용하기보다 실제로 필요한 출처, 메서드, 헤더만 허용하는 것이 좋습니다.
2. Express의 cors 모듈 사용
Node.js와 Express 환경에서는 cors 미들웨어를 이용해 필요한 응답 헤더와 프리플라이트 처리를 설정할 수 있습니다.
npm install cors
const express = require("express");
const cors = require("cors");
const app = express();
app.use(
cors({
origin: "https://frontend.example",
methods: ["GET", "POST"],
credentials: true,
})
);
아무 옵션 없이 app.use(cors())를 사용하면 모든 출처에 응답을 공개할 수 있습니다. 공개 API가 아니라면 특정 출처와 필요한 요청 방식만 허용하도록 옵션을 설정하는 것이 안전합니다.
3. webpack-dev-server의 프록시 사용
프론트엔드와 API 서버의 출처가 다른 로컬 개발 환경에서는 webpack-dev-server의 프록시 기능을 사용할 수 있습니다.
// webpack.config.js
module.exports = {
devServer: {
proxy: [
{
context: ["/api"],
target: "http://localhost:3000",
changeOrigin: true,
},
],
},
};
브라우저에서 /api/users로 요청하면 개발 서버가 이를 API 서버로 전달합니다. 브라우저의 관점에서는 프론트엔드 개발 서버와 같은 출처에 요청하므로 개발 중 발생하는 CORS 문제를 피할 수 있습니다.
다만 이 방법은 개발 환경을 위한 프록시 설정입니다. 운영 환경에서는 웹 서버의 리버스 프록시를 구성하거나 API 서버에서 올바른 CORS 헤더를 반환해야 합니다.
클라이언트에서 해결할 수 있을까?
일반적인 CORS 오류는 프론트엔드의 fetch나 Axios 옵션만 바꿔서 해결할 수 없습니다. 허용 여부를 나타내는 헤더는 요청받는 서버의 응답에 포함되어야 합니다.
또한 브라우저의 보안 기능을 끄거나 임의의 공개 프록시를 사용하는 방법은 개발 과정의 임시 확인 외에는 적절한 해결책이 아닙니다. 서버에서 필요한 출처만 정확히 허용하는 것이 기본 원칙입니다.
한 줄 요약
CORS는 서버가 허용한 교차 출처의 웹 애플리케이션에 브라우저가 응답 접근 권한을 부여하는 HTTP 헤더 기반 체제이며, 서버의 응답 헤더나 CORS 미들웨어로 설정하고 개발 환경에서는 프록시를 활용할 수 있습니다.
참고