CORS Error 는 왜 자주 발생할까? — 교차 출처 리소스 공유

2025. 10. 18. 20:57·Computer Science/Web Development

프론트엔드 개발자라면 한 번쯤 겪었을 그 문제 CORS Error. 이번 글에서는 CORS(Cross-Origin Resource Sharing)의 개념부터 원리, 해결 전략까지 깔끔히 정리해본다.

 

1. CORS란?

CORS (Cross-Origin Resource Sharing) 은 브라우저가 보안을 위해 다른 출처(origin)의 리소스 요청을 제한하는 정책이다.

즉, 브라우저는 자신이 실행 중인 웹 페이지와 다른 도메인으로의 요청을 기본적으로 차단한다. 이는 XSS(Cross-Site Scripting) 나 CSRF(Cross-Site Request Forgery) 같은 보안 공격을 방지하기 위한 핵심 메커니즘이다.

 

2. “출처(Origin)”란 무엇인가?

하나의 Origin(출처) 은 다음 세 가지로 구성된다.

구성 요소 예시
Scheme (Protocol) https://
Host (Domain) www.naver.com
Port (Port Number) :443

세 가지가 모두 같아야 동일 출처로 간주한다. 단 하나라도 다르면 교차 출처(Cross-Origin) 요청이 된다.

 

예시

요청 URL 동일 출처? 이유
https://example.com/api ✅ 완전히 동일
http://example.com/api ❌ 프로토콜 다름
https://api.example.com ❌ 서브도메인 다름
https://example.com:3000 ❌ 포트 다름

 

3. 왜 CORS 에러가 발생할까?

브라우저는 보안을 위해 “다른 출처로의 요청”을 자체적으로 차단한다. 하지만 실제 서비스에서는 프론트엔드와 백엔드가 서로 다른 도메인이나 포트에서 동작하는 경우가 많다.

 

예를 들어:

// 1. 도메인이 다른 경우
Frontend: https://app.myservice.com
Backend:  https://api.myservice.com

// 2. 포트번호가 다른 경우
Frontend: http://localhost:3000
Backend:  http://localhost:4000

이 경우 브라우저는 이렇게 판단한다. “이건 다른 Origin이다. 이 요청이 안전한지 모르겠다. 서버에게 CORS 정책을 확인해야겠다.”

 

그리고 서버가 아래 응답 헤더를 주면 요청이 허용된다.

1. Access-Control-Allow-Origin: https://app.myservice.com 
2. Access-Control-Allow-Origin: http://localhost:3000

서버가 이 헤더를 명시하지 않으면 브라우저는 응답을 버리고 콘솔에 CORS Error를 띄운다.

 

4. 브라우저가 CORS를 처리하는 이유

CORS는 브라우저 단에서만 적용된다. 서버-서버 간 통신(cURL, Postman, Node.js 요청 등)은 이 정책의 영향을 받지 않는다.

 

왜냐하면, “서버는 신뢰된 환경에서 통신하지만, 브라우저는 사용자의 보안이 걸린 공개된 클라이언트 환경이기 때문이다.”

즉, 브라우저(클라이언트)는 “서버가 이 데이터를 특정 출처(Origin)에 제공해도 된다고 허락했는지" 확인하고, 응답 접근을 차단하거나 허용한다. 예를 들어, 누군가 악성 스크립트를 심은 사이트에 사용자가 접속했을 때, 그 스크립트가 임의로 다른 사이트(예: 은행, 이메일 서버)에 요청을 보내더라도, 브라우저는 그 응답 데이터에 접근하는 것을 차단하여 민감한 정보가 유출되는 것을 방지한다.

 

5. 요청 흐름 이해하기

  • 악성 스크립트를 심은 사이트는 사용자가 방문한 “위험한 클라이언트 페이지”
  • 다른 사이트(예: 은행, 이메일 서버) 등은 요청을 받는 서버 (다른 출처 Origin)
  • 브라우저는 두 곳 사이에서 요청과 응답을 중계하면서 보안을 감시하는 존재

예시 요청 흐름을 살펴보자.

1. 사용자가 malicious-site.com 에 접속했다. (이건 클라이언트, 즉 React 앱이나 HTML 페이지를 의미)

2. 이 사이트 내부의 악성 스크립트가 https://bank.com/api/accounts 같은 다른 출처의 서버에 요청을 시도한다.

fetch("https://bank.com/api/accounts")

 

3. 브라우저는 요청을 보내기 전에 생각한다.

"잠깐, 지금 페이지(malicious-site.com)에서 다른 출처(bank.com)로 요청을 보내고 있네? 이건 CORS 정책 위반일 수도 있겠는데?” 그래서 브라우저는 bank.com 서버에 “이 요청 허용할까요?”라는 의미로 Preflight 요청(OPTIONS) 을 먼저 보낸다.

4. 만약 bank.com 이 응답 헤더로

Access-Control-Allow-Origin: https://malicious-site.com

 

를 보내지 않으면, 브라우저는 이 요청을 차단한다.

 

위 예시처럼, CORS 요청은 두 가지 방식으로 나뉜다.

구분 설명
Preflight Request (사전 요청) OPTIONS 메서드로 미리 서버에 “이 요청을 보내도 괜찮은가?”를 묻는 과정
Simple Request GET/POST 요청 중, 특정 헤더와 MIME 타입만 포함된 단순 요청

이처럼 서버가 사전 요청에 올바르게 응답해야 CORS Error 가 발생하지 않고 실제 API 호출이 이어진다.

# 예시: Preflight 요청 응답 헤더
Access-Control-Allow-Origin: https://secure-site.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true

 

6. 쿠키와 인증 정보가 필요한 경우

교차 출처로 요청 시, 쿠키나 세션 정보를 포함하려면 클라이언트와 서버 모두 별도의 설정이 필요하다.

 

프론트엔드 (React, Axios 등)

axios.get('https://api.example.com/user', {
  withCredentials: true
});

백엔드 (Express.js)

res.setHeader('Access-Control-Allow-Origin', 'https://myapp.com');
res.setHeader('Access-Control-Allow-Credentials', 'true');

이렇게 해야 브라우저가 쿠키를 함께 전송하고 응답도 정상적으로 수신한다.

 

7. 개발 중 CORS 문제 해결 방법

  1. 서버 설정 수정 (가장 권장되는 방법)
    • 백엔드에서 Access-Control-Allow-Origin 헤더를 추가한다.
    • 필요 시 도메인별 화이트리스트를 관리한다.
  2. *프록시(proxy) 설정 (개발 환경용)
    • 로컬 개발 시 동일 출처처럼 동작하도록 중간 서버를 둔다.
    • 예: CRA → setupProxy.js, Next.js → rewrites 설정 사용
  3. 브라우저 플러그인 (비추천)
    • 개발 테스트 용도로만 사용한다.
    • 보안상 위험하므로 배포 환경에서는 절대 사용하지 않는다.

*프록시 서버

  • 브라우저는 CORS 정책 때문에 다른 출처(origin)의 API를 바로 호출할 수 없다.
  • 프록시 서버는 브라우저 → 같은 출처 → 프록시 → 외부 API로 요청을 전달한다.
  • 브라우저 입장에서는 프록시 서버가 동일 출처이므로 CORS 문제가 발생하지 않는다.
  • 장점: 인증, 토큰, 헤더 등 민감 정보를 숨길 수 있고, 개발/운영 환경 모두에 적용 가능하다.
[브라우저] ---> [내 서버 / 프록시] ---> [외부 API 서버]

1.브라우저 요청

  • 사용자가 프론트엔드에서 /api/data를 요청한다.
  • 브라우저 입장에서는 요청이 동일 출처로 향하기 때문에 CORS 오류가 발생하지 않는다.

 

React 개발 환경 (CRA)에서 프록시 설정 예제:

// package.json
{
  "name": "my-app",
  "version": "0.1.0",
  "dependencies": { ... },
  "proxy": "http://localhost:4000"
}
// React에서 fetch 요청
fetch('/api/data') // 실제로는 localhost:4000/api/data로 전달
  .then(res => res.json())
  .then(data => console.log(data));
  • 개발 환경에서 /api/... 요청은 모두 http://localhost:4000/api/...로 프록시된다.
  • 브라우저는 프록시 서버와 동일 출처로 통신한다고 인식하여 CORS 문제 발생 없음.

2. 프록시 서버 처리

  • 프록시 서버가 브라우저 요청을 받아 내부적으로 외부 API로 전달한다.
  • 필요 시 인증 토큰이나 헤더를 추가해 안전하게 외부 API와 통신한다.

 

Express 서버에서 프록시 구현 예제:

const express = require('express');
const fetch = require('node-fetch');
const app = express();

app.use(express.json());

// 프록시 엔드포인트
app.get('/api/data', async (req, res) => {
  try {
    // 외부 API 호출
    const apiRes = await fetch('https://external-api.com/data');
    const data = await apiRes.json();
    // 브라우저에 그대로 전달
    res.json(data);
  } catch (err) {
    res.status(500).json({ error: 'Failed to fetch data' });
  }
});

app.listen(4000, () => console.log('Proxy server running on port 4000'));
  • 브라우저에서는 fetch('/api/data')로 요청
  • 프록시 서버가 외부 API 호출 후 응답을 브라우저로 전달
  • 브라우저는 동일 출처로 요청했으므로 CORS 문제 없음

3.외부 API 응답

  • 외부 API가 데이터를 반환한다.
  • 프록시 서버는 받은 응답을 브라우저로 그대로 전달한다.

 

운영 환경 (Nginx) 프록시 예제:

server {
    listen 80;
    server_name my-frontend.com;

    location /api/ {
        proxy_pass https://external-api.com/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location / {
        root /var/www/html;
        index index.html;
    }
}
  • 브라우저는 my-frontend.com/api/...로 요청
  • Nginx 프록시가 외부 API로 요청 전달 → 응답 브라우저로 전달
  • 브라우저 입장에서는 동일 출처 응답으로 인식 → CORS 문제 없음

4. 브라우저에서 응답 처리

  • 브라우저는 프록시 서버로부터 응답을 받아 처리한다.
  • 사용자에게 데이터를 표시하고, 필요 시 UI 업데이트를 진행한다.

 

정리: 브라우저 → 프록시 서버 → 외부 API → 프록시 서버 → 브라우저 순서로 동작하며, 이 과정을 통해 CORS 문제 없이 외부 API 데이터를 안전하게 가져올 수 있다.

 

8. 실무 팁

상황 해결 방향
API 서버가 외부에 있음 서버에서 Access-Control-Allow-Origin을 설정한다.
로그인/세션 포함 요청 withCredentials + Allow-Credentials: true 설정을 사용한다.
로컬 개발 중 테스트 프록시 설정으로 동일 출처처럼 구성한다.
특정 도메인만 허용 와일드카드(*) 대신 명시적 도메인을 지정한다.

 

9. 정리 및 마무리

CORS는 “서버 간 통신이 아닌, 브라우저의 보안 장치”다. CORS는 복잡해 보이지만 결국 서버가 어떤 출처의 요청을 신뢰할지 명시하는 제도다. 보안을 위해 존재하지만, 프론트엔드 개발자에게는 가장 자주 등장하는 실무 이슈 중 하나다.

 

CORS 에러를 만났다면

  • 서버 응답 헤더를 먼저 확인하고,
  • 요청이 단순한지(Preflight 여부),
  • 쿠키/인증 정보가 필요한지

를 순서대로 점검한다.

 

“CORS는 버그가 아니라, 브라우저의 방패다.”

'Computer Science > Web Development' 카테고리의 다른 글

주소창에 google.com을 입력하면 일어나는 일  (0) 2025.10.18
웹 서버(Nginx, Apache..) 의 역할  (0) 2025.10.18
JWT(Json Web Token)  (0) 2025.10.17
SPA, CSR, SSR, 그리고 Hybrid Rendering — 웹 렌더링의 진화  (0) 2025.10.17
z-index가 같아도 겹치는 순서가 달라지는 이유  (0) 2025.10.16
'Computer Science/Web Development' 카테고리의 다른 글
  • 주소창에 google.com을 입력하면 일어나는 일
  • 웹 서버(Nginx, Apache..) 의 역할
  • JWT(Json Web Token)
  • SPA, CSR, SSR, 그리고 Hybrid Rendering — 웹 렌더링의 진화
JTB
JTB
웹/앱 개발 정보를 공유하고 있습니다.
  • JTB
    JTechBlog
    JTB
  • 전체
    오늘
    어제
    • All About Programming;)
      • Computer Science
        • Terminology and Concepts
        • Network
        • Operating System
        • Database
        • Data Structure
        • Web Development
      • Frontend
        • Javascript Essentials
        • Perfomance Optimization
        • JS Patterns
        • React
        • Next.js
        • Flutter
        • Testing
      • Backend
        • Node.js
      • DevOps
        • Docker & Kubernetes
      • Coding Test
        • LeetCode
        • Programmers
      • Tech Books & Lectures
        • Javascript_Modern JS Deep d..
        • Network_IT 엔지니어를 위한 네트워크 입문
      • Projects
        • PolyLingo_2025
        • Build Your Body_2024
        • JStargram_2021
        • Covid19 Tracker_2021
        • JPortfolio_2021
      • BootCamp_Codestates
        • TIL
        • TILookCloser
        • Pre Tech Blog
        • IM Tech Blog
        • Daily Issues and DeBugging
        • First Project
        • Final Project
        • Sprint Review
        • Good to Know
        • Socrative Review
        • HTML & CSS
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 글쓰기
    • 관리
  • 공지사항

  • 인기 글

  • 태그

    커리어
    mobile app
    VoiceJournal
    testing
    need a database
    프론트엔드 성능 최적화 가이드
    딥다이브
    js pattern
    database
    Threads and Multithreading
    자바스크립트 딥다이브
    자바스크립트
    How memory manage data
    Network
    이벤트
    Data Structure
    Binary Tree BFS
    Shared resources
    Operating System
    모던 자바스크립트 Deep Dive
    TCP/IP
    DOM
    Time complexity and Space complexity
    CPU scheduling algorithm
    Javascript Essentials
    스코프
    polylingo
    structure of os
    leetcode
    indie hacker
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
JTB
CORS Error 는 왜 자주 발생할까? — 교차 출처 리소스 공유
상단으로

티스토리툴바