Access-Control-Allow-Origin 헤더 완벽 가이드
CORS 오류의 원인인 Access-Control-Allow-Origin 헤더를 완벽히 이해하고 해결하세요. 문법, 보안, Nginx/Node.js 설정 예시까지 다룹니다.
웹 개발을 하다 보면 한 번쯤은 콘솔에서 CORS error라는 무시무시한 메시지를 마주하게 됩니다. 이 오류는 대부분 다른 출처(origin)의 리소스를 요청할 때 발생하는데요, 바로 이 문제를 해결하는 열쇠가 Access-Control-Allow-Origin 헤더에 있습니다.
이 글에서는 교차 출처 리소스 공유(CORS)의 핵심인 Access-Control-Allow-Origin 헤더가 무엇인지, 어떻게 동작하는지, 그리고 실제 서버 환경에서는 어떻게 설정해야 하는지 자세히 알아보겠습니다. 이 글을 끝까지 읽으시면 CORS 오류에 자신감 있게 대처할 수 있을 거예요!
Access-Control-Allow-Origin 이란 무엇인가요?
Access-Control-Allow-Origin은 HTTP 응답 헤더 중 하나입니다. 서버가 클라이언트에게 "이 응답은 특정 출처(origin)와 공유해도 괜찮아"라고 알려주는 역할을 하죠.
웹 브라우저는 보안상의 이유로 '동일 출처 정책(Same-Origin Policy)'을 따릅니다. 이 정책은 기본적으로 스크립트가 자신이 로드된 출처와 다른 출처의 리소스와 상호작용하는 것을 막습니다. 예를 들어, https://my-awesome-site.com에서 실행되는 스크립트는 https://api.another-site.com의 API를 직접 호출할 수 없습니다.
하지만 현대 웹 애플리케이션은 여러 서비스의 API를 호출하는 등 다른 출처의 리소스를 사용하는 것이 필수적입니다. 이때 필요한 것이 바로 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS) 메커니즘이며, Access-Control-Allow-Origin 헤더는 이 메커니즘의 가장 기본적이고 중요한 부분입니다.
CORS의 동작 방식은 간단합니다.
- 브라우저가 다른 출처로 HTTP 요청을 보낼 때, 요청 헤더의
Origin필드에 요청을 보내는 출처를 담아 보냅니다. - 요청을 받은 서버는 이 요청을 허용할지 결정하고, 응답 헤더의
Access-Control-Allow-Origin필드에 접근을 허용하는 출처를 명시하여 응답합니다. - 응답을 받은 브라우저는 자신이 보낸 요청의
Origin값과 서버가 보내준Access-Control-Allow-Origin값을 비교합니다. 두 값이 일치하면 리소스를 정상적으로 로드하고, 일치하지 않으면 CORS 오류를 발생시키며 리소스를 차단합니다.
문법과 사용법
Access-Control-Allow-Origin 헤더는 다음과 같은 세 가지 주요 값을 가질 수 있습니다.
Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: <origin>
Access-Control-Allow-Origin: null
각 값이 어떤 의미를 가지는지 자세히 살펴보죠.
* (와일드카드)
가장 간단한 설정 방법입니다. *는 "모든 출처에서 오는 요청을 허용한다"는 의미입니다. 어떤 도메인에서든 내 서버의 리소스에 접근할 수 있게 되죠.
Access-Control-Allow-Origin: *
개발 초기 단계나 공개 API처럼 출처에 상관없이 누구나 접근할 수 있는 리소스를 제공할 때 유용합니다. 하지만 인증 정보(credentials)가 포함된 요청에는 *를 사용할 수 없다는 중요한 제약이 있습니다. 쿠키나 인증 헤더를 사용하는 요청에 *로 응답하면 브라우저가 보안 오류를 발생시킵니다.
<origin> (특정 출처)
가장 안전하고 권장되는 방법입니다. https://my-awesome-site.com과 같이 리소스 접근을 허용할 정확한 출처를 명시합니다.
Access-Control-Allow-Origin: https://developer.mozilla.org
이렇게 설정하면 https://developer.mozilla.org에서 보낸 요청에 대해서만 리소스를 공유하고, 그 외 다른 모든 출처의 요청은 브라우저에 의해 차단됩니다. 하나의 출처만 명시할 수 있으므로, 여러 출처를 허용해야 한다면 서버 측에서 요청의 Origin 헤더 값을 확인하고 허용 목록에 있는 값으로 동적으로 설정해주어야 합니다.
null
null 값은 약간 특수한 경우에 사용됩니다. 예를 들어, 로컬 파일(file://)에서 요청을 보내거나 리다이렉트가 발생하는 경우 Origin 헤더 값이 null이 될 수 있습니다. 하지만 보안상 null을 명시적으로 허용하는 것은 매우 신중해야 합니다.
흔히 겪는 문제와 해결 방법
CORS 관련 문제 중 가장 흔한 것은 인증 정보(credentials)가 포함된 요청이 실패하는 경우입니다.
fetch API나 XMLHttpRequest를 사용할 때 credentials: 'include' 옵션을 주면 쿠키나 인증 헤더와 같은 인증 정보를 요청에 담아 보낼 수 있습니다. 하지만 이 경우, 서버는 다음과 같은 규칙을 반드시 지켜야 합니다.
Access-Control-Allow-Origin헤더 값으로*와일드카드를 사용해서는 안 됩니다. 반드시 요청을 보낸 특정 출처를 명시해야 합니다.Access-Control-Allow-Credentials: true헤더를 응답에 포함해야 합니다. 이 헤더가 없으면 브라우저는 인증 정보가 포함된 응답을 거부합니다.
만약 서버가 와일드카드(*)를 사용하면서 인증을 허용하려고 하면, 브라우저는 이 응답을 유효하지 않은 것으로 간주하고 오류를 발생시킵니다. 따라서 인증이 필요한 API를 개발할 때는 반드시 허용할 출처를 명확하게 지정하는 것이 중요합니다.
보안 고려사항
Access-Control-Allow-Origin: * 설정은 편리하지만, 보안 측면에서는 잠재적인 위험을 안고 있습니다. 이 설정은 악의적인 사이트를 포함한 모든 출처가 여러분의 서버 리소스에 접근할 수 있도록 허용하기 때문입니다.
예를 들어, 내부에서만 사용해야 하는 API를 *로 설정해두면 외부에서 해당 API를 무단으로 호출하여 정보를 탈취하거나 서버에 부담을 줄 수 있습니다.
따라서 프로덕션 환경에서는 다음과 같은 보안 모범 사례를 따르는 것이 좋습니다.
- 허용 목록(Allowlist) 사용: 리소스 접근이 필요한 출처들을 미리 정의해두고, 서버는 요청의
Origin헤더가 이 목록에 포함된 경우에만 해당 출처를Access-Control-Allow-Origin값으로 설정하여 응답합니다. - 최소 권한의 원칙: 꼭 필요한 출처에만 접근을 허용하고, 가능하면
*사용은 피하세요.
서버 설정 예시
실제 서버 환경에서 Access-Control-Allow-Origin 헤더를 어떻게 설정하는지 Nginx와 Node.js(Express)의 예시를 통해 알아보겠습니다.
Nginx 설정 예시
Nginx에서는 add_header 지시어를 사용하여 간단하게 헤더를 추가할 수 있습니다.
1. 모든 출처 허용 (비권장)
# nginx.conf
location / {
add_header 'Access-Control-Allow-Origin' '*';
# ... 다른 설정들
}
2. 특정 출처만 허용 (권장)
# nginx.conf
location / {
add_header 'Access-Control-Allow-Origin' 'https://your-domain.com';
add_header 'Vary' 'Origin'; # 캐싱 관련 이슈를 방지하기 위해 추가
# ... 다른 설정들
}
서버가 특정 출처를 응답으로 보낼 때는, 응답이 Origin 요청 헤더에 따라 달라질 수 있음을 클라이언트와 중간 캐시 서버에 알려주기 위해 Vary: Origin 헤더를 함께 보내는 것이 좋습니다.
Node.js (Express) 설정 예시
Express 프레임워크에서는 미들웨어를 사용하여 모든 응답에 CORS 헤더를 쉽게 추가할 수 있습니다. cors와 같은 라이브러리를 사용하면 더 편리하지만, 직접 구현하는 방법은 다음과 같습니다.
1. 모든 출처 허용 (비권장)
// app.js
const express = require('express');
const app = express();
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
// 필요한 다른 CORS 헤더들 (Access-Control-Allow-Methods 등)
next();
});
// ... 라우트 설정
2. 특정 출처만 허용 (권장)
// app.js
const express = require('express');
const app = express();
const allowedOrigins = ['http://localhost:3000', 'https://your-domain.com'];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.setHeader('Access-Control-Allow-Credentials', 'true');
// ... 필요한 다른 CORS 헤더들
next();
});
// ... 라우트 설정
더 나은 웹을 위한 첫걸음
CORS의 동작 원리를 정확히 이해하고 올바르게 설정하는 것은 안전하고 효율적인 웹 애플리케이션을 만드는 개발자의 기본 소양이라고 할 수 있습니다. 오늘 배운 내용을 바탕으로 여러분의 프로젝트에서 CORS 설정을 다시 한번 점검해보고, 더 안전한 웹 서비스를 만들어나가시길 바랍니다!