CORS가 뭐길래? 웹 개발자가 반드시 알아야 할 교차 출처 리소스 공유 (1부)

웹 개발을 하다 보면 한 번쯤은 마주치게 되는 빨간색 오류 메시지, “Access to XMLHttpRequest has been blocked by CORS policy“… 처음 보면 당황스럽고 해결 방법을 찾기도 어렵죠. 여러분은 열심히 코드를 작성했는데 브라우저가 갑자기 리소스 접근을 차단해버립니다! 오늘은 웹 개발자라면 꼭 알아야 할 CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)에 대해 알아보겠습니다.

CORS는 단순히 오류 메시지가 아니라 웹 보안의 중요한 메커니즘입니다. 이 글 시리즈에서는 CORS가 무엇인지, 왜 필요한지, 어떻게 작동하는지, 그리고 개발 중 CORS 관련 문제를 어떻게 해결할 수 있는지 명확하게 설명해드리겠습니다. 특히 프론트엔드와 백엔드 개발을 오가는 개발자라면 더욱 꼼꼼히 읽어보세요!

이 첫 번째 글에서는 CORS의 기본 개념과 작동 원리에 대해 중점적으로 알아보겠습니다. 2부에서는 실제 구현 방법과 디버깅 전략, 모범 사례에 대해 다룰 예정이니 함께 살펴보시죠! <br>


🔒 웹 브라우저의 보안 장벽, CORS의 등장 배경

웹 브라우저는 기본적으로 동일 출처 정책(Same-Origin Policy, SOP)이라는 보안 정책을 따릅니다. 이 정책은 한 출처(origin)에서 로드된 문서나 스크립트가 다른 출처의 리소스와 상호작용하는 것을 제한합니다. 출처란 프로토콜(http, https), 도메인, 포트의 조합을 말합니다.

예를 들어 https://lazybug.io에서 실행 중인 JavaScript가 https://api.example.com의 데이터를 가져오려고 할 때, 이는 서로 다른 출처이기 때문에 기본적으로 차단됩니다. 왜 이런 제한이 있을까요?

동일 출처 정책이 없다면 어떤 일이 벌어질지 상상해보세요:

  • 여러분이 은행 웹사이트에 로그인한 상태로
  • 악의적인 웹사이트를 방문하면
  • 그 사이트의 스크립트가 은행 API에 요청을 보내 자금을 이체할 수 있습니다

😱 무시무시하죠? 동일 출처 정책은 이런 CSRF(Cross-Site Request Forgery) 공격을 방지하는 중요한 첫 번째 방어선입니다.

하지만 현대 웹은 여러 출처의 리소스를 활용하는 구조로 발전했습니다. 프론트엔드와 백엔드 API를 분리하는 것이 일반적이죠. 이런 상황에서 합법적인 교차 출처 요청을 허용하면서도 보안을 유지할 방법이 필요했고, 그래서 CORS가 탄생했습니다.


🤝 CORS가 정확히 하는 일은?

CORS는 브라우저와 서버 간의 안전한 교차 출처 요청을 가능하게 하는 메커니즘입니다. 핵심은 서버가 브라우저에게 “이 출처의 요청은 괜찮아, 허용해줘”라고 말할 수 있게 해주는 것이죠.

이것이 어떻게 작동하는지 단계별로 살펴봅시다:

  1. 브라우저가 다른 출처로 요청을 보냅니다
  2. 서버는 특정 HTTP 헤더로 응답합니다
  3. 브라우저는 이 헤더를 확인하여 요청을 허용할지 결정합니다

가장 중요한 CORS 헤더는 Access-Control-Allow-Origin입니다. 이 헤더는 서버가 어떤 출처의 요청을 허용하는지 지정합니다.

Access-Control-Allow-Origin: https://lazybug.io

위 헤더는 https://lazybug.io에서 오는 요청만 허용한다는 의미입니다. 모든 출처를 허용하려면 와일드카드를 사용할 수 있습니다:

Access-Control-Allow-Origin: *

⚠️ 하지만 와일드카드 사용은 보안상 권장되지 않습니다! 꼭 필요한 출처만 지정하는 것이 좋습니다.


🛫 프리플라이트 요청: CORS의 숨겨진 보안 계층

CORS의 가장 흥미로운 부분 중 하나는 프리플라이트 요청(Preflight Request)입니다. 이건 뭘까요?

단순하지 않은 요청(non-simple request)의 경우, 브라우저는 실제 요청을 보내기 전에 OPTIONS 메서드로 예비 요청을 먼저 보냅니다. 이 예비 요청은 “내가 이런 요청을 보내도 괜찮을까요?”라고 서버에게 물어보는 것과 같습니다.

단순하지 않은 요청이란 다음 조건 중 하나라도 해당되는 경우입니다:

  • GET, HEAD, POST 외의 HTTP 메서드 사용
  • Content-Type이 application/x-www-form-urlencoded, multipart/form-data, text/plain 이외의 값
  • 커스텀 헤더 포함

실제 상황에서 어떻게 작동하는지 보여드리겠습니다:

// 프론트엔드 코드
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',  // 이 헤더로 인해 프리플라이트 요청 발생
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify({ name: 'LazBug' })
})

이 코드를 실행하면 브라우저는 다음과 같은 프리플라이트 요청을 먼저 보냅니다:

OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: https://lazybug.io
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

서버는 이렇게 응답해야 합니다:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://lazybug.io
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400  // 24시간 동안 프리플라이트 결과를 캐시

이 응답이 적절하다면, 브라우저는 실제 POST 요청을 진행합니다. 그렇지 않으면 CORS 오류가 발생합니다.


CORS의 개념, 이제 조금 더 명확해졌나요?

지금까지 CORS의 개념, 등장 배경, 그리고 프리플라이트 요청에 대해 알아보았습니다. CORS는 웹 브라우저의 보안 메커니즘인 동일 출처 정책을 안전하게 우회할 수 있는 표준 방법입니다. 웹 개발에서 프론트엔드와 백엔드가 분리되는 현대적인 아키텍처에서는 필수적인 개념이죠.

요약하자면,

  • CORS는 브라우저의 동일 출처 정책을 안전하게 우회할 수 있는 표준 메커니즘입니다
  • 서버 측 설정을 통해 특정 출처의 요청을 허용하거나 거부할 수 있습니다
  • 프리플라이트 요청은 실제 요청 전에 서버에게 권한을 확인하는 중요한 보안 계층입니다

CORS의 기본 개념을 이해했다면, 이제 실제로 어떻게 구현하고 문제를 해결할 수 있는지 알아볼 차례입니다. 2부에서는 다양한 환경에서의 CORS 설정 방법, 디버깅 전략, 그리고 모범 사례에 대해 알아보겠습니다.

CORS에 대한 이해를 더 깊게 하고 웹 개발 중 마주치는 CORS 관련 문제를 해결하는 능력을 키우고 싶다면, 계속해서 2부를 읽어보세요!

“CORS가 뭐길래? 웹 개발자가 반드시 알아야 할 교차 출처 리소스 공유 (1부)”에 대한 1개의 생각

댓글 남기기