5. IT기술노트/인프라&개발

CORS란 무엇인가? - 브라우저 보안의 핵심과 실무 이해

쿼드큐브 2025. 10. 20. 20:29
반응형

반응형

 

CORS란 무엇인가? - 브라우저 보안의 핵심과 실무 이해

웹 애플리케이션을 개발하다 보면, 정상적인 API 요청임에도 불구하고 브라우저가 이를 거부하는 상황을 경험하게 됩니다.
이러한 현상의 근본적인 원인은 브라우저의 보안 정책(Same-Origin Policy)에 있으며, 이를 예외적으로 제어하기 위한 기술이 바로 CORS (Cross-Origin Resource Sharing)입니다.


본 문서에서는 CORS가 등장하게 된 보안적 배경, 프로토콜 수준의 동작 원리, 그리고 실무 환경에서의 설정 및 대응 전략을 체계적으로 살펴봅니다.

CORS란 무엇인가? 삽화 이미지
CORS란 무엇인가? 삽화 이미지

 

1. 브라우저는 왜 CORS가 필요할까요?

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

Same-Origin Policy (SOP)는 브라우저가 사용자의 데이터를 보호하기 위해 도입한 가장 근본적인 보안 메커니즘입니다.

이 정책은 한 웹페이지의 스크립트가 다른 출처(origin)의 자원에 임의로 접근하거나 조작하는 것을 차단합니다.

 

예를 들어,
사용자가 https://bank.com에 로그인한 상태에서
악성 사이트 http://malicious-site.com을 방문했다고 가정하겠습니다.
SOP가 없다면, 악성 스크립트가 bank.com의 인증 쿠키나 계좌 정보에 접근할 수 있게 됩니다.

 

즉, SOP는 세션 정보·개인 데이터·금융 정보 등의 민감한 자산을 보호하기 위한 기본 보안 장치입니다.

 

🔷 보안과 편의성의 충돌

문제는 SOP가 너무 엄격하다는 점입니다.

보안을 위해 모든 “서로 다른 출처” 간 요청을 차단하다 보니, 정상적이고 합법적인 API 호출 조차 브라우저가 막는 상황이 발생합니다.


예를 들어,
프런트엔드가 https://myapp.com에서 실행되고
백엔드 API 서버가 https://api.myapp.com에 있는 경우,
서브도메인이 다르기 때문에 브라우저는 이를 “다른 출처”로 간주하고 요청을 차단합니다.

보안 측면에서는 타당하지만, 개발 환경에서는 이로 인해 서비스 간 통신이 불가능해집니다.

 

🔷 CORS의 등장: 보안을 유지하며 예외를 허용하는 방법

CORS는 이 문제를 해결하기 위해 고안된 HTTP 기반 메커니즘입니다.

“기본적으로 모든 교차 출처 요청을 차단”하되, 서버가 명시적으로 허용한 출처(origin)에 한해서만 자원 공유를 허용합니다.


즉, 브라우저가 요청을 보낼 때 서버에 “이 출처를 허용하겠는가?”를 묻고, 서버가 명시적으로 동의하면 요청을 수락하는 협상 과정입니다.

 

이로써 브라우저는
▸ 보안성(Same-Origin Policy의 원칙)과
▸ 유연성(필요한 경우 출처 간 통신 허용)을 동시에 확보할 수 있게 되었습니다.

 

2. CORS란 무엇인가 - 개념과 동작 원리

🔷 CORS의 정의

Cross-Origin Resource Sharing(CORS) 은

“서로 다른 출처(origin) 간의 HTTP 요청을 서버가 선택적으로 허용하도록 하는 헤더 기반 보안 메커니즘”입니다.

 

CORS는 HTTP 요청 헤더와 응답 헤더를 통해 서버가 어떤 출처에서의 요청을 신뢰할지 명시적으로 선언하도록 합니다.

브라우저는 이 정보를 검증하여 응답을 스크립트에 전달할지 여부를 결정합니다.

 

🔷 Origin(출처)의 정의

출처는 다음 세 요소의 조합으로 결정됩니다.

구성 요소 예시 설명
프로토콜(Scheme) https:// HTTP와 HTTPS는 서로 다른 출처로 간주됩니다.
도메인(Host) example.com 도메인이 다르면 출처도 다릅니다.
포트 번호(Port) :8080, :443 포트가 다르면 동일 도메인이라도 다른 출처로 판단됩니다.

 

예를 들어 다음 세 주소는 모두 서로 다른 출처로 인식됩니다.

▸ https://example.com
▸ http://example.com
▸ https://example.com:3000

즉, 프로토콜, 도메인, 포트 중 하나라도 다르면 브라우저는 ‘서로 다른 출처’로 간주하며, 이때 발생하는 요청은 기본적으로 차단됩니다.

 

🔷 CORS의 동작 절차

1. 요청 생성

브라우저는 요청 헤더에 Origin을 포함시켜 전송합니다.

Origin: https://myapp.com

 

2. 서버 응답

서버는 요청을 검토한 뒤, 허용 시 다음 헤더를 반환합니다.

Access-Control-Allow-Origin: https://myapp.com

또는 특정 상황에서는 전체 허용(*)을 명시할 수도 있습니다.

Access-Control-Allow-Origin: *

 

3. 브라우저 검증

응답 수신 후 브라우저는 Access-Control-Allow-Origin 값이 자신의 Origin과 일치하는지 확인하고, 허용되지 않으면 응답을 스크립트에 전달하지 않습니다.

⚠️ CORS policy error
“Access to fetch at ‘https://api.example.com’
 from origin ‘https://myapp.com’
 has been blocked by CORS policy.”

이 검증은 브라우저 레벨에서 수행되며, 서버는 요청을 정상적으로 처리했더라도 브라우저가 응답 액세스를 차단할 수 있습니다.

 

🔷 요청 유형 분류

✔️ Simple Request (단순 요청)

▸ 메서드: GET, HEAD, POST

▸ 허용 Content-Type: application/x-www-form-urlencoded, multipart/form-data, text/plain
▸ 커스텀 헤더 미사용

 

✔️ Preflight Request (사전 점검 요청)

▸ 조건에 해당하지 않는 요청(예: PUT, DELETE, 커스텀 헤더 사용)은 먼저 OPTIONS 메서드로 서버 허용 정책을 질의합니다.

OPTIONS /data
Origin: https://myapp.com
Access-Control-Request-Method: DELETE

 

▸ 이에 대해 서버는 다음과 같은 응답을 반환해야 합니다.

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 600

Access-Control-Max-Age는 Preflight 결과를 브라우저 캐시에 저장할 수 있는 시간(초)입니다.

 

✔️ 인증 정보가 포함된 요청 (Credentialed Request)

쿠키, 세션 토큰, Authorization 헤더 등을 포함한 요청은 CORS에서 가장 엄격한 검사를 받습니다.

 

다음 두 조건을 모두 만족해야 합니다.

1. 클라이언트 측에서 fetch('url', { credentials: 'include' }) 또는 XMLHttpRequest.withCredentials = true 설정

2. 서버 응답에 다음 헤더 포함

Access-Control-Allow-Credentials: true

이 경우 Access-Control-Allow-Origin 은 * (와일드카드)을 사용할 수 없습니다. 반드시 특정 출처(예: https://myapp.com)를 명시해야 합니다.

이는 인증 정보가 모든 사이트로 유출되는 것을 방지하기 위한 보안 강화 조치입니다.

 

반응형

 

3. 입문자를 위한 실전 팁과 주의사항

🔷 1) CORS 설정의 핵심 원칙

CORS 정책은 서버가 명시적으로 허용한 출처(origin) 내에서만 교차 출처 요청을 승인합니다.

즉, 브라우저는 서버의 응답 헤더를 기반으로 허용 여부를 판단하며, 클라이언트 측 코드 수정만으로는 CORS 오류를 해결할 수 없습니다.

기본 원칙은 다음과 같습니다.

항목 설명
허용 출처(origin) 특정 도메인을 명시적으로 지정 (Access-Control-Allow-Origin: https://myapp.com)
전체 허용(*) 개발 중 임시 사용은 가능하지만, 운영 환경에서는 보안상 권장되지 않음
인증 요청(credentials) Access-Control-Allow-Credentials: true 설정 시 반드시 명시적 출처 지정 필요 (* 불가)
HTTP 메서드 허용 Access-Control-Allow-Methods 에서 필요한 메서드만 열기
헤더 제한 Access-Control-Allow-Headers 로 필요한 최소 헤더만 지정

 

🔷 2) 서버 환경별 설정 예시

▸ Express.js (Node.js)

import express from "express";
import cors from "cors";

const app = express();

// 보안: 명시적 출처만 허용
const corsOptions = {
  origin: "https://myapp.com",
  methods: ["GET", "POST", "DELETE"],
  credentials: true,
};

app.use(cors(corsOptions));

이 설정은 https://myapp.com 출처에서 오는 요청만 허용하며, 쿠키나 인증 토큰이 포함된 요청(credentials)도 안전하게 처리합니다.

origin: "*" 형태는 테스트용으로만 사용해야 하며, credentials가 설정된 요청에서는 브라우저가 이를 무시합니다.

 

▸Spring Boot (Java)

@CrossOrigin(origins = "https://myapp.com",
             allowedHeaders = "*",
             allowCredentials = "true",
             methods = {RequestMethod.GET, RequestMethod.POST})

Spring 프레임워크는 @CrossOrigin 애너테이션을 통해 개별 컨트롤러 또는 전역 정책으로 CORS 규칙을 지정할 수 있습니다.
공식 문서에서도 allowedOrigins("*") 은 인증 요청이 포함된 경우 사용하지 말 것을 권장하고 있습니다.

 

▸ Nginx Reverse Proxy

location /api/ {
    add_header 'Access-Control-Allow-Origin' 'https://myapp.com';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
    add_header 'Access-Control-Allow-Credentials' 'true';
}

프록시 서버(Nginx, Apache 등)에서도 동일한 원칙으로 CORS 응답 헤더를 지정할 수 있습니다.
이 방식은 API 서버를 직접 수정하기 어려운 경우 유용합니다.

 

🔷 3) 개발 환경에서의 임시 조치

로컬 개발 환경에서는 프런트엔드(localhost:5173)와 백엔드(localhost:4000)가 서로 다른 포트에서 동작하는 경우가 많습니다.
브라우저는 이를 “서로 다른 출처”로 인식하여 기본적으로 CORS를 차단합니다.

 

▸ 개발용 프록시(proxy) 설정

개발 서버(Vite, Vue CLI, CRA 등)는 프록시 기능을 제공하여 브라우저가 “같은 출처처럼” 인식하도록 중계할 수 있습니다.

Vue (Vite 기반) 예시:

// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      "/api": {
        target: "http://localhost:4000",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    },
  },
});

이 설정은 프런트엔드 개발 서버가 요청을 대신 전송하여 브라우저가 동일 출처로 인식하도록 만드는 임시 개발용 우회 방식입니다.
보안 정책을 변경하는 것이 아니라, CORS 검사를 회피하는 로컬 개발 편의 기능이라는 점을 명확히 인지해야 합니다.

 

▸ 임시 전체 허용 (‘*’) 사용

테스트 단계에서는 모든 출처를 허용하도록 설정할 수 있지만, 반드시 운영 배포 전에는 제한된 도메인으로 수정해야 합니다.

// 개발용 서버 예시 (Express)
app.use(cors({ origin: "*" }));

 

응답 헤더 예시:

Access-Control-Allow-Origin: *

 

이 설정은 단기간 테스트용으로는 유용하지만, 다음과 같은 제약이 있습니다.

제약 설명
보안 취약성 모든 출처에서 요청이 가능하므로 악성 스크립트 접근 위험 존재
credentials 미지원 인증 정보가 포함된 요청에서는 브라우저가 응답을 무시
운영 환경 부적합 외부 사이트에서 자원을 무단 요청할 수 있음

 

📘 공식 문서 인용
“If the server’s response includesAccess-Control-Allow-Origin: *, the response will not include credentials.”
즉, *와 Access-Control-Allow-Credentials: true는 동시에 사용할 수 없습니다.

 

🔷 4) 보안 및 운영상 권고 사항

CORS 정책은 단순한 기능 설정이 아니라 보안 협약(Security Contract)입니다.

잘못된 설정은 즉시 보안 취약점으로 이어질 수 있으므로 다음 원칙을 지키는 것이 중요합니다.

권고 사항 설명
① *와 credentials 동시 사용 금지 브라우저가 응답을 무시하거나 쿠키가 노출될 위험이 있음
② 허용 출처 화이트리스트 사용 여러 도메인을 허용할 경우 배열 형태로 명시 관리
③ 사전 검증(Preflight) 캐시 기간 설정 Access-Control-Max-Age 값으로 불필요한 OPTIONS 트래픽 최소화
④ 배포 전 정책 검증 curl -i -X OPTIONS 또는 브라우저 DevTools를 통해 실제 응답 헤더 확인

 

 

반응형

 

※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.

반응형