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
  • 블로그 메뉴

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

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

  • 인기 글

  • 태그

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

  • 최근 글

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

티스토리툴바