CORS 에러 : 원인 분석부터 올바른 보안 설정까지

Hack hack 아바타

교차 출처 리소스 공유(CORS, Cross-Origin Resource Sharing)는 웹 브라우저가 다른 도메인의 리소스에 접근할 수 있도록 허용하는 보안 메커니즘입니다.

현대 웹 개발에서 프론트엔드와 백엔드 서버를 분리하여 운영하는 구조가 일반화되면서, 올바른 CORS 설정은 안전한 데이터 통신을 구축하기 위한 필수 과제입니다. 이 글에서는 CORS 에러의 원인을 분석하고, 서비스 환경에 맞춘 올바른 해결 방법을 정리합니다.

1. CORS 에러 발생 원인과 통신 구조

CORS 에러의 근본적인 원인은 웹 브라우저의 기본 보안 정책인 동일 출처 정책(SOP, Same-Origin Policy) 때문입니다. 브라우저는 사용자가 접속한 도메인과 다른 도메인(출처)에서 가져온 데이터는 보안상 신뢰할 수 없다고 판단하여 차단합니다. 여기서 출처(Origin)는 프로토콜(http/https), 도메인, 포트 번호의 조합을 뜻합니다.

예를 들어 로컬 개발 환경에서 다음과 같이 통신할 때 브라우저는 이를 ‘교차 출처’로 인식합니다.

  • 요청 출처 (프론트엔드): http://localhost:3000
  • 대상 출처 (백엔드 서버): http://localhost:8080

데이터가 차단되는 구체적인 시각적 흐름은 다음과 같습니다. 서버는 요청을 받아 정상적으로 데이터를 응답하지만, 최종 단계에서 브라우저가 보안 검사를 수행한 뒤 응답을 파기합니다.

[프론트엔드: 3000포트] ──(API 요청)──> [백엔드 서버: 8080포트]
                                               │
[웹 브라우저 (SOP 검사)] <──(데이터 응답)───────┘
     │
     └──> ❌ 허가 헤더가 없으므로 블로킹 (CORS 에러 발생)

첨부된 CORS 에러 터미널 이미지(watermarked_img_16187838490390302080.png)에서 확인할 수 있듯이, 브라우저가 접근을 차단하면 네트워크 수준에서 연결이 실패하며 Access-Control-Allow-Origin 헤더가 누락되었다는 메시지가 출력됩니다.

2. 환경별 해결 방법

CORS 정책을 위반하지 않고 정상적으로 통신하려면 백엔드에서 명시적 허가를 주거나, 프론트엔드에서 브라우저의 눈을 속이는 프록시 설정을 해야 합니다.

방법 1: 백엔드 서버에서 CORS 허용 헤더 추가 (추천)

가장 정석적인 해결책은 데이터를 제공하는 백엔드 서버의 응답 헤더에 프론트엔드 도메인을 허용 리스트로 등록하는 것입니다.

Node.js / Express 환경 설정 예시:

const express = require('express');
const cors = require('cors');
const app = express();
// 특정 프론트엔드 출처만 명시적으로 허용
app.use(cors({
origin: 'http://localhost:3000',
credentials: true
}));

Spring Boot 환경 설정 예시:

@RestController
@CrossOrigin(origins = "http://localhost:3000")
public class ApiController {
// API 컨트롤러 로직
}

방법 2: 프론트엔드 개발용 Proxy 설정

배포 전 로컬 개발 단계에서 빠르게 에러를 우회해야 하거나 백엔드 코드를 수정할 수 없는 상황이라면, 프론트엔드 개발 서버를 중계 서버(Proxy)로 활용할 수 있습니다.

React(Create React App) 환경의 package.json 설정:

{
"name": "frontend-project",
"version": "0.1.0",
"proxy": "http://localhost:8080"
}

이 설정을 적용하면 브라우저는 프론트엔드 서버와 동일한 출처로 요청을 보내는 것으로 인식하여 SOP 정책을 통과하게 되며, 프론트엔드 서버가 뒤에서 백엔드로 데이터를 요청하여 전달해 줍니다.

3. 배포 시 필수 보안 주의사항

CORS 에러를 빠르게 해결하기 위해 백엔드 설정에서 허용 범위를 와일드카드(origin: '*')로 지정하는 경우가 많습니다. 이는 **모든 외부 도메인에서 내 백엔드 서버에 접근하여 데이터를 가져갈 수 있도록 문을 열어두는 심각한 보안 취약점**을 야기합니다.

타인에 의해 API 오남용이나 데이터 무단 탈취(CSRF 공격 등)가 발생할 수 있으므로, 실제 서비스 배포 환경에서는 반드시 https://service.com과 같이 신뢰할 수 있는 특정 운영 도메인만 지정하는 화이트리스트 방식을 고수해야 합니다.

4. 자주 묻는 질문 (FAQ)

Q1. 포스트맨(Postman)으로 호출할 때는 왜 CORS 에러가 안 나나요?
CORS는 웹 브라우저가 사용자를 보호하기 위해 강제하는 보안 규칙입니다. 포스트맨이나 모바일 앱은 브라우저 환경이 아니므로 SOP 검사 로직 자체가 존재하지 않아 정상적으로 응답을 받아옵니다.

Q2. 실제 데이터 요청 전에 발생하는 Preflight(예비 요청)는 무엇인가요?
브라우저는 데이터 형식을 수정하는 POST, PUT, DELETE 같은 요청을 보낼 때, 본 요청을 보내기 전 서버가 안전한지 확인하기 위해 OPTIONS 메서드를 사용하는 예비 요청(Preflight)을 먼저 보냅니다. 이 예비 요청에서 CORS 허가가 떨어져야만 실제 본 요청이 전달됩니다.

Q3. CORS 설정을 적용하면 백엔드 서버 자체의 보안이 강화되나요?
아닙니다. CORS는 백엔드 서버를 보호하는 방화벽이 아니라, **사용자의 브라우저를 보호하는 장치**입니다. 서버 자체의 무단 접근을 막으려면 별도의 인증 메커니즘(JWT, OAuth)이나 방화벽 설정을 적용해야 합니다.

Q4. credentials 옵션은 언제 사용하나요?
프론트엔드와 백엔드가 서로 다른 도메인 상태에서 쿠키(Cookie)나 세션 토큰을 주고받아야 할 때 사용합니다. 이 옵션을 true로 설정할 경우, 보안 규칙상 백엔드의 와일드카드(*) 설정은 강제로 금지됩니다.

5. 핵심 요약 및 마치며

CORS 에러를 해결할 때 반드시 기억해야 할 핵심 메시지는 다음과 같습니다.

  • CORS 에러는 서버 오류가 아닌 브라우저의 정상적인 보안 작동 결과입니다.
  • 로컬 개발 환경에서는 프론트엔드 Proxy 설정으로 빠르게 우회할 수 있습니다.
  • 실제 운영 서버 배포 시에는 와일드카드(*)를 배제하고 허용 도메인을 명확히 지정해야 보안 사고를 예방할 수 있습니다.

올바른 출처 기준을 파악하여 안전하고 견고한 웹 서비스를 구축하시기 바랍니다.


댓글 남기기

sys-hack에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기