리액트 스프링 부트 CORS 오류 해결 프록시 미들웨어와 어노테이션으로 완벽하게 잡는 가이드
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
리액트 스프링 부트 CORS 오류 해결 프록시 미들웨어와 어노테이션으로 완벽하게 잡는 가이드
안녕하세요. 15년 차 백엔드 개발자이자, 수많은 주니어 개발자들의 멘토로 활동하고 있는 선배 개발자입니다. 오늘 우리가 다룰 주제는 리액트(React)와 스프링 부트(Spring Boot)를 연동하여 프로젝트를 진행할 때, 개발자라면 누구나 한 번쯤은 반드시 마주치게 되는 공포의 빨간 글씨, 바로 CORS(Cross-Origin Resource Sharing) 정책 위반 오류입니다. ☕
아마 여러분도 이런 경험이 분명히 있으실 겁니다. 프론트엔드에서 API를 호출하는 코드를 완벽하게 작성하고, 로컬 환경에서 부푼 기대를 안고 테스트를 돌렸는데 브라우저 콘솔 창에 붉은색 에러 메시지가 뜹니다. "Access to fetch at... from origin... has been blocked by CORS policy." 이 메시지를 보는 순간, 머릿속이 하얘지고 '어제는 분명히 잘 됐는데 왜 이러지?'라는 생각부터 들죠. 더 황당하고 억울한 건, 포스트맨(Postman)이나 curl 명령어로 요청을 보내면 서버가 너무나도 정상적으로 200 OK 응답을 준다는 사실입니다. "내 코드는 완벽한데, 브라우저가 나를 미워하나?"라는 생각이 들 정도로 답답한 상황이 연출됩니다.
사실 저도 신입 시절, 이 CORS 문제 때문에 꼬박 3일을 밤새우며 고생한 적이 있습니다. 서버 설정도 바꿔보고, 프론트엔드 코드도 뜯어고쳐 보고, 심지어 브라우저 보안 설정을 끄고 개발하기도 했었죠. 하지만 이런 임시방편은 결국 실제 배포(Production) 단계에서 더 큰 폭탄으로 돌아오더군요. 그때 뼈저리게 깨달았습니다. "아, 이건 단순히 에러 메시지를 없애는 게 중요한 게 아니라, 웹의 보안 동작 원리를 근본적으로 이해해야 하는 문제구나"라고요. 오늘은 제가 15년 동안 대규모 트래픽을 처리하며 겪은 수많은 시행착오와 실전 프로젝트 경험을 바탕으로, 이 지긋지긋한 CORS 문제를 완벽하고 우아하게 해결하는 방법을 A부터 Z까지 알려드리겠습니다.
단순히 "이 코드를 복사해서 붙여넣으세요" 식의 얕은 설명은 절대 하지 않겠습니다. 왜 이 문제가 발생하는지, 브라우저는 대체 무슨 생각으로 요청을 막는 것인지, 그리고 상황별로 가장 적합한 해결책은 무엇인지를 아주 깊이 있게 파고들 예정입니다. 리액트의 프록시 설정부터 스프링 부트의 어노테이션, 스프링 시큐리티 설정, 그리고 실무 노하우까지 모든 무기를 꺼내드릴 테니 저만 믿고 따라오세요. 이 글을 다 읽고 나면, 동료가 CORS 때문에 괴로워할 때 여유롭게 웃으면서 도와줄 수 있는 진짜 고수가 되어 있을 겁니다. 자, 그럼 시작해볼까요? 🔥
1. 도대체 CORS가 뭐길래 우리를 괴롭힐까? (원리 심층 분석)
해결책을 논하기 전에, 적을 먼저 명확히 알아야 합니다. CORS는 'Cross-Origin Resource Sharing', 즉 '교차 출처 리소스 공유'의 약자입니다. 이름부터가 뭔가 복잡해 보이지만, 핵심은 간단합니다. 브라우저는 기본적으로 보안을 위해 동일 출처 정책(Same-Origin Policy, SOP)이라는 엄격한 규칙을 따릅니다. 이는 마치 여러분의 집 현관문과도 같습니다. 가족(동일 출처)이라면 열쇠 없이도 자유롭게 드나들 수 있지만, 낯선 사람(다른 출처)이 들어오려 하면 일단 문을 걸어 잠그고 확인하는 것이죠.
출처(Origin)란 정확히 무엇인가?
여기서 말하는 '출처(Origin)'가 무엇인지 명확히 이해하는 것이 문제 해결의 첫걸음입니다. 출처는 프로토콜(Protocol), 호스트(Host), 포트(Port) 이 세 가지의 조합으로 정의됩니다. 예를 들어보겠습니다. 만약 여러분이 리액트 애플리케이션을 http://localhost:3000에서 실행하고 있고, 스프링 부트 서버는 http://localhost:8080에서 실행 중이라고 가정해 봅시다. 이 둘은 같은 'localhost'라는 호스트를 쓰지만, 포트 번호가 3000과 8080으로 서로 다릅니다. 브라우저 입장에서는 이 둘을 완전히 다른 출처로 인식합니다. 마치 같은 아파트(localhost)에 살지만, 3000호와 8080호는 엄연히 다른 집인 것과 같습니다.
리액트(3000호)가 스프링 부트(8080호)의 냉장고 문을 열고 데이터를 꺼내오려(Fetch) 하면, 브라우저라는 경비원이 나타나서 "잠깐! 너희 둘은 다른 집이잖아. 집주인(서버)이 허락했어?"라고 묻는 것이 바로 CORS 검사 과정입니다. 만약 8080호 주인이 "아, 3000호 사람은 우리 집에 와도 돼요"라고 HTTP 헤더를 통해 명시적으로 허락해 주지 않았다면, 브라우저는 보안을 위해 요청 자체를 차단해 버립니다. 이것이 우리가 보는 CORS 에러의 실체입니다. 통계적으로 개발 초기 단계에서 발생하는 네트워크 에러의 약 40%가 이 출처 불일치에서 기인한다고 합니다.
프리플라이트(Preflight) 요청의 비밀
단순 요청(GET 등) 외에, POST나 PUT처럼 서버의 데이터를 변경할 수 있는 요청을 보낼 때 브라우저는 더욱 신중해집니다. 이때 브라우저는 본 요청을 보내기 전에 예비 요청(Preflight Request)이라는 것을 먼저 보냅니다. 이때 사용되는 HTTP 메서드가 바로 OPTIONS입니다. "나 지금 이거 보내도 돼?"라고 먼저 찔러보는 것이죠. 만약 서버가 이 OPTIONS 요청에 대해 적절한 CORS 헤더를 포함하여 200 OK를 주지 않으면, 브라우저는 본 요청을 아예 시도조차 하지 않고 에러를 뱉습니다. 많은 개발자가 "POST 요청을 보냈는데 왜 403 에러가 뜨지?"라고 의아해하지만, 네트워크 탭을 자세히 보면 실제로는 OPTIONS 요청에서 막힌 경우가 대다수입니다.
2. 해결책 1: 스프링 부트 전역 설정 (WebMvcConfigurer)
가장 먼저 소개할 방법은 백엔드, 즉 스프링 부트 쪽에서 "나는 이 출처의 접근을 허용한다"라고 공식적으로 선언하는 방법입니다. 실무에서 가장 권장되는 표준 방식이며, 코드 관리가 용이하여 유지보수 측면에서도 가장 깔끔합니다. 보통 WebMvcConfigurer 인터페이스를 구현하여 설정을 관리합니다.
WebMvcConfigurer 구현 예시
스프링 부트 프로젝트 내에 설정 클래스(Configuration Class)를 하나 만듭니다. 보통 WebConfig 혹은 CorsConfig라는 이름으로 짓습니다. 이 클래스에서 addCorsMappings라는 메서드를 오버라이딩하여 CORS 규칙을 정의합니다. 다음은 실무에서 바로 사용할 수 있는 모범 코드입니다.
여기서 allowedOrigins에 *(와일드카드)를 사용하는 것은 매우 주의해야 합니다. 특히 allowCredentials(true) 옵션을 켤 경우, 보안상의 이유로 * 사용이 불가능하며 반드시 구체적인 도메인을 명
💬 여러분의 경험을 들려주세요!
✨ 이 방법을 시도해보셨나요? 댓글로 공유해주세요!
📌 도움이 되셨다면 저장하고 주변에도 알려주세요.
🔔 더 많은 개발 팁을 받고 싶다면 구독해주세요!
이 글이 도움되셨나요? 공유해주세요!
아래 링크를 통해 구매 시 운영자에게 일정 수수료가 발생할 수 있습니다.
'리액트와 스프링 부트 연동 시 발생하는 CORS 정책 위반 오류를 프록시 미들웨어 설정이나 어노테이션으로 해결하는 가이드' 관련 상품을 쿠팡에서 확인해 보세요.
상품 보러가기 →- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기