본문 바로가기
보안

웹 보안 기초 - CORS 쉽게 이해하기

by 들판에 날라다니는 솜 2026. 7. 1.

CORS(Cross-Origin Resource Sharing)란?

CORS(Cross-Origin Resource Sharing)는*서로 다른 출처(Origin) 간의 리소스 공유를 안전하게 허용하기 위한 브라우저의 보안 메커니즘이다.


CORS가 필요한 이유

웹 브라우저에서는 여러 사이트를 자유롭게 방문할 수 있다. 하지만 이러한 특성 때문에 악성 사이트가 사용자의 브라우저를 이용해 다른 사이트에 요청을 보내는 보안 문제가 발생할 수 있다

예를 들어 우리가 인터넷 뱅킹 사이트에 로그인한 상태에서 악성 사이트를 방문했다고 가정해 보자.

만약 브라우저에 아무런 제한이 없다면 악성 사이트는 다음과 같은 요청을 보낼 수도 있다.

fetch("https://bank.com/api/transfer", {
  method: "POST",
  body: JSON.stringify({
    account: "123456",
    money: 1000000,
  }),
});
 

이런 요청이 자유롭게 허용된다면 사용자의 개인정보나 금융 정보가 쉽게 탈취될 수 있다.

이러한 문제를 막기 위해 브라우저에는 동일 출처 정책(Same-Origin Policy, SOP)이라는 보안 정책이 존재한다.


동일 출처 정책(Same-Origin Policy)

동일 출처 정책(Same-Origin Policy, SOP)은 현재 페이지와 동일한 출처에서 제공된 리소스에만 자유롭게 접근할 수 있도록 제한하는 브라우저의 보안 정책이다.

출처는 다음 세 가지 요소가 모두 같아야 한다.

  • 프로토콜(Protocol)
  • 도메인(Domain)
  • 포트(Port)

예를 들어 현재 웹사이트가 다음 주소라고 가정해 보자.

http://localhost:3000
 

다음 주소들과 비교해 보면 다음과 같다.

요청 주소동일 출처 여부
http://localhost:3000 O
http://localhost:8080 X (포트가 다름)
https://localhost:3000 X (프로토콜이 다름)
http://api.localhost:3000 X (도메인이 다름)

즉, 하나라도 다르면 다른 출처(Cross Origin)가 된다.


그렇다면 다른 출처와는 통신할 수 없는 걸까?

그렇지는 않다.

예를 들어 React 프로젝트는 다음과 같이 실행되는 경우가 많다.

React
http://localhost:3000

Spring Boot
http://localhost:8080
 

프론트엔드는 포트 3000, 백엔드는 포트 8080처럼 서로 다른 서버에서 실행된다.

하지만 프론트엔드는 백엔드 API를 호출해야 하므로 서로 다른 출처끼리 통신할 수 있어야 한다.

이처럼 서로 다른 출처 간의 통신을 안전하게 허용하기 위해 사용하는 것이 CORS이다.


CORS란?

CORS(Cross-Origin Resource Sharing)는 서버가 특정 출처에 대해 리소스 접근을 허용하도록 브라우저에게 알려주는 메커니즘이다.

많은 사람들이 "CORS는 브라우저 에러"라고 생각하지만 사실은 그렇지 않다.

실제로는 다음과 같은 순서로 동작한다.

  1. 브라우저가 서버에 요청을 보낸다.
  2. 서버는 정상적으로 응답을 반환한다.
  3. 브라우저는 응답 헤더를 확인한다.
  4. 허용된 출처라면 JavaScript가 응답을 사용할 수 있다.
  5. 허용되지 않았다면 브라우저가 응답을 차단하고 CORS 오류를 발생시킨다.

즉, CORS는 요청 자체를 막는 것이 아니라 브라우저가 응답을 사용할 수 있는지를 결정하는 보안 정책이다.


동작 과정

예를 들어 React에서 API를 호출한다고 해보자.

 
fetch("http://localhost:8080/api/users");
 

동작 과정은 다음과 같다.

React
    │
    │ GET /api/users
    ▼
Spring Boot
    │
    │ Access-Control-Allow-Origin
    ▼
Browser
    │
    ├─ 허용 → JavaScript에서 응답 사용 가능
    └─ 거부 → CORS Error 발생
 

예를 들어 서버가 다음과 같은 응답 헤더를 보낸다면

Access-Control-Allow-Origin: http://localhost:3000
 

브라우저는

"http://localhost:3000에서 온 요청이므로 응답을 사용할 수 있겠구나."

라고 판단하여 JavaScript가 응답을 사용할 수 있도록 허용한다.

반대로 해당 헤더가 없거나 현재 출처가 허용 목록에 없다면 브라우저는 응답을 차단하고 CORS 오류를 발생시킨다.


Access-Control-Allow-Origin

가장 많이 사용하는 CORS 응답 헤더이다.

예를 들어

Access-Control-Allow-Origin: http://localhost:3000
 

이라면

http://localhost:3000
 

에서 오는 요청만 허용된다.

모든 출처를 허용하려면 다음과 같이 사용할 수도 있다.

Access-Control-Allow-Origin: *
 

하지만 실무에서는 *를 사용하는 경우가 많지 않다.

특히 로그인이나 인증 정보를 사용하는 API에서는 보안상 사용할 수 없는 경우가 많기 때문에, 허용할 출처를 명시적으로 지정하는 것이 일반적이다.


Preflight 요청이란?

모든 요청이 바로 서버로 전달되는 것은 아니다.

브라우저는 일부 요청을 위험할 수 있는 요청으로 판단하면 실제 요청을 보내기 전에 먼저

OPTIONS
 

요청을 전송하여 서버가 해당 요청을 허용하는지 확인한다.

이 과정을 Preflight 요청이라고 한다.

동작 과정은 다음과 같다.

Browser
   │
   │ OPTIONS
   ▼
Server
   │
   │ 허용 여부 응답
   ▼
Browser
   │
   ├── 허용 → 실제 요청 전송
   └── 거부 → 요청 차단
 

언제 Preflight가 발생할까?

대표적인 경우는 다음과 같다.

  • PUT 요청
  • DELETE 요청
  • PATCH 요청
  • Authorization 헤더 사용
  • Content-Type이 application/json

예를 들어

 
fetch("/api/users", {
  method: "POST",
  headers: {
    Authorization: "Bearer token",
    "Content-Type": "application/json",
  },
});
 

이 경우에는 대부분 실제 POST 요청보다 먼저 OPTIONS 요청이 전송된다.

실무에서는 브라우저 개발자 도구의 Network 탭에서 OPTIONS 요청이 먼저 보이는데, 이것이 바로 Preflight 요청이다.


CORS를 해결하는 방법

CORS는 프론트엔드에서 해결하는 문제가 아니라 서버에서 허용 설정을 해야 해결된다.

예를 들어 Spring Boot에서는 허용할 출처를 설정할 수 있다.

http://localhost:3000
 

또는

https://my-service.com
 

처럼 접근을 허용할 출처를 명시한다.

개발 환경에서는 프록시(Proxy)를 사용해 동일 출처처럼 요청을 전달하는 방법도 자주 사용한다.

다만 이는 개발 편의를 위한 방법이며, 운영 환경에서는 API 서버가 올바른 CORS 정책을 설정하는 것이 바람직하다.


실무에서 알아두면 좋은 점

  • CORS는 브라우저에서만 적용되는 보안 정책이다.
  • 서버 간 통신은 일반적으로 CORS의 영향을 받지 않는다.
  • Access-Control-Allow-Origin: *는 인증 정보를 사용하는 API에는 적합하지 않다.
  • OPTIONS 요청이 보인다면 Preflight 요청일 가능성이 높다.
  • CORS 오류가 발생했다면 프론트엔드 코드보다 서버의 CORS 설정을 먼저 확인하는 것이 좋다.

'보안' 카테고리의 다른 글

CSRF(Cross-Site Request Forgery)란?  (0) 2026.07.02
XSS(Cross-Site Scripting)란?  (0) 2026.07.01