[React] 🛠 CORS 오류를 해결하자 (SSL? HTTPS? HTTP?)
0. 들어가면서
백엔드와 함께 협업 프로젝트를 진행하다보면 은근 자주 마주치게 되는 CORS 오류
최근에도 이 오류를 마주쳐 임시방편으로 프록시로 해결을 하고 서버 측에서 허용을 해줌으로써
오류를 해결하였지만 정확히 알아보고자 포스팅을 하게 되었다.
1. CORS란 무엇인가?
✅ CORS 개념 및 원리
CORS(Cross-Origin Resource Sharing)는
브라우저에서 보안상의 이유로 다른 출처(Origin)의 리소스 요청을 제한하는 정책이다.
다른 말로 서로 다른 출처의 리소스를 안전하게 주고받을 수 있도록 해주는 보안 정책이다.
출처(Origin)는 프로토콜 + 도메인 + 포트(번호)로 결정된다.
예를 들어,
- https://example.com:3000 → https://example.com:4000 요청 시 CORS 정책 적용됨
- https://example.com → http://example.com 요청 시에도 CORS 적용됨
✅ 왜 CORS 정책이 필요한가?
다음과 같은 몇 가지 이유가 있다.
- 보안 위협으로부터 웹페이지를 보호합니다.
- 복잡한 애플리케이션에서 서드 파티 API 및 리소스를 참조할 때 유용합니다.
- 다양한 출처의 리소스를 활용해야 하는 현대의 웹 환경에선 꼭 필요하다.
- 웹 사이트 간의 악의적인 요청을 방지하기 위해 적용된다.
보안 위협, 악의적인 요청 관련 예를 들자면,
공격자가 사용자가 로그인한 은행 웹사이트의 API를 호출하여 데이터 유출을 시도하는 것을 막기 위함이다.
사실 이전까지 나는 현직 개발자가 아니기에 CORS 정책의 필요성과 중요성을 체감하진 못했다.
이번 포스팅을 통해 이를 좀 알아두면 좋을 거 같다..!
(백엔드 사람들과 협업 프로젝트를 진행할 때 CORS 오류 때문에 귀찮으면 귀찮았지..)
2. CORS 오류가 발생하는 이유
✅ 브라우저 보안 정책과 CORS
웹 브라우저는 기본적으로 Same-Origin Policy(SOP)라는 보안 정책을 따르며, 이는 동일 출처가 아닌 경우 리소스 요청을 제한한다. SOP는 웹페이지가 자신과 동일한 출처에서만 리소스를 요청할 수 있도록 제한한다.
이 때 콘솔에서 우리는 CORS 오류가 발생한 것을 볼 수 있다.
여기서 말하는 출처는 위에서 설명한 출처(Origin) = 프토코롤 + 도메인 + 포트(번호)를 말한다.
이 중 하나만 달라도 서로 다른 출처로 인식하고 리소스 요청을 제한한다.
다시 돌아와서 현대의 애플리케이션들은 여러 다양한 API와 상호작용하며, 트래픽이 커지면 커질수록 서버 또한 여러 개를 사용하기도 하기에 다른 출처의 리소스를 사용할 수 있는 방법이 필요해져 CORS가 도입됐다.
정리하자면, CORS는 기존 브라우저 보안 정책, SOP에 부분적으로 허용을 하게 하는 정책인 것이다.
✅ HTTP 요청 방식(GET, POST 등)에 따른 차이
CORS 오류는 HTTP 요청 방식에 따른 차이로 발생하기도 한다.
- GET 요청: 단순 요청(Simple Request)으로 간주되어 서버 응답 헤더에 Access-Control-Allow-Origin이 포함되어 있으면 허용됨. 되어 있지 않다면 CORS 오류 발생
- POST/PUT/DELETE 요청: 브라우저는 먼저 OPTIONS 메서드로 "preflight request"를 보내 허용 여부를 확인한다. 마찬가지로 허용이 되어 있지 않다면 CORS 오류 발생
자세한 CORS 동작 방식(해결 방식)은 아래에서 살펴보겠다.
✅ preflight request(OPTIONS 요청)의 역할
브라우저는 실제 요청 전에 OPTIONS 메서드를 사용해 서버가 특정 출처의 요청을 허용하는지 확인한다.
OPTIONS 응답에 Access-Control-Allow-Methods, Access-Control-Allow-Headers 등이 포함되어야 한다.
그렇지만 서버 측에서 이 요청을 허용하지 않아서 CORS 오류가 발생하는 일이 다반사였다.
3. CORS 오류 해결 방법
✅ 서버에서 Access-Control-Allow-Origin 설정하기
대표적으로는 위의 제목과 같이
백엔드 서버의 응답 헤더에 Access-Control-Allow-Origin: * 또는 특정 도메인을 지정해 CORS 허용하는 방법이 있다.
하지만 * 를 통해 모든 출처(Origin) 요청을 허용하면 아무래도 보안이 취약해질 것이다.
CORS는 웹 서버가 어떤 출처에서의 요청을 허용할지를 결정할 수 있도록 한다.
이를 위해 웹 서버는 다음과 같은 HTTP 헤더를 설정한다.
- Access-Control-Allow-Origin: 허용된 출처를 지정합니다. 예: https://www.example.com
- Access-Control-Allow-Methods: 허용된 HTTP 메서드를 지정합니다. 예: GET, POST
- Access-Control-Allow-Headers: 허용된 요청 헤더를 지정합니다. 예: Content-Type
- Access-Control-Allow-Credentials: 자격 증명(쿠키, 인증 헤더 등)의 포함 여부를 지정합니다. 예: true
CORS요청에는 두 가지 유형과 각 특징을 살펴보자.
- Simple Request: 서버 상태를 조회하기 위한 간단한 요청(GET, HEAD 등)- 브라우저가 사전에 확인 없이 바로 서버에 요청을 보냄- 서버가 Access-Control-Allow-Origin을 응답 헤더에 포함하면 정상 처리됨
다음 세 가지 조건을 모두 만족해야 Simple Request로 간주됨:
- 허용된 메서드만 사용: GET, HEAD, POST(POST는 경우에 따라 바뀌긴 한다) 중 하나
- 허용된 헤더만 포함:
- Accept, Content-Type, Origin 등 기본적인 헤더만 가능
- Content-Type은 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용
- XMLHttpRequest 또는 fetch 사용 시 credentials: include 설정 안 함
- Preflight Request: 서버 상태를 변경할 수 있는 요청(POST, PUT 등). 서버에 사전 검증(OPTIONS)을 요청- 브라우저가 실제 요청 전에 OPTIONS 메서드를 사용하여 서버가 요청을 허용하는지 확인, 허용하면 이후 실제 요청을 보냄
다음 중 하나라도 해당되면 Preflight Request가 발생함:
- PUT, DELETE, PATCH 등 안전하지 않은 HTTP 메서드 사용
- Authorization, X-Custom-Header 등 커스텀 헤더 포함
- Content-Type이 application/json 등 기본 허용되지 않은 값
아래는 각 CORS의 동작 방식의 예시 코드이다.
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "https://your-frontend.com");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
next();
});
두 요청의 차이점을 표로 정리해보았다.
CORS 요청 차이점
구분 | Simple Request(단순 요청) | Preflight Request(사전 요청) |
메서드 | GET, HEAD, POST | PUT, DELETE, PATCH, POST (조건 충족 시) |
헤더 제한 | 기본 헤더만 (Content-Type: text/plain 등) | 커스텀 헤더 가능 (Authorization 포함 등) |
사전 요청 (OPTIONS 요청) | ❌ 없음 | ✅ 있음 (브라우저가 사전 확인 요청) |
사용 예시 | 일반적인 API 요청 (GET으로 데이터 가져오기) | 인증이 필요한 API 요청 (POST로 JSON 데이터 전송 등) |
✅ 프록시 서버 활용 (proxy 설정)
프론트엔드에서 개발할 때 CORS 문제를 우회하기 위해 프록시 서버를 설정하는 방법이다.
서버 측에서 해결을 해주기 전에 주로 사용했었던 방법이다.
vite.config.ts에서 설정 예시:
export default defineConfig({
server: {
proxy: {
"/api": {
target: "https://your-backend.com",
changeOrigin: true,
secure: false,
},
},
},
});
✅ 브라우저 확장 프로그램 활용
테스트 환경에서는 Moesif CORS, Allow CORS 등의 확장 프로그램을 이용하는 방법도 있다고 한다.
4. HTTP vs. HTTPS & SSL 인증서
마지막으로 살펴볼 부분은
웹 개발에서 보안은 중요한 요소이며, HTTP와 HTTPS의 차이, 그리고 SSL 인증서의 역할을 이해하는 것이 필수적이다.
✅ HTTP와 HTTPS의 차이
- HTTP(HyperText Transfer Protocol): 평문(Plain Text)으로 데이터를 주고받음.
- HTTPS(HyperText Transfer Protocol Secure): SSL/TLS 암호화를 사용하여 보안 강화.
구분 | HTTP (HyperText Transfer Protocol) |
HTTPS (HyperText Transfer Protocol Secure) |
보안 | 암호화 없음 (데이터 평문 전송) | SSL/TLS 암호화로 보안 강화 |
포트 번호 | 80번 | 443번 |
속도 | 상대적으로 빠름 | SSL 암호화 과정 때문에 약간 느림 |
데이터 보호 | 없음 (데이터가 평문으로 전송되어 가로채기 가능) | 데이터 암호화, 무결성 보장 |
SEO (검색엔진 최적화) | SEO 점수 낮음 (Google에서 HTTPS 사이트를 우선적으로 노출) | SEO 점수 상승 (Google 권장) |
브라우저 표시 | 주소창에 "주의 요함" 또는 "안전하지 않음" 표시 | 🔒 자물쇠 아이콘 표시 |
📌 HTTP의 문제점
- 데이터 평문 전송 → 해커가 데이터를 중간에서 가로채는 **MITM 공격(Man-In-The-Middle Attack)**에 취약함
- 신뢰할 수 없는 환경 → 사용자 로그인 정보, 카드 정보 등이 쉽게 노출될 위험이 있음
- SEO 불이익 → 구글, 네이버 등의 검색엔진이 HTTPS 웹사이트를 우선적으로 노출
📌 HTTPS의 동작 방식
HTTPS는 SSL/TLS 프로토콜을 활용하여 데이터를 암호화하여 안전하게 통신한다.
- 클라이언트(브라우저)가 HTTPS 웹사이트에 접속
- 서버가 SSL 인증서를 제공하여 신뢰할 수 있는 사이트임을 증명
- 클라이언트와 서버가 SSL/TLS 핸드셰이크를 수행하여 암호화 키 교환
- 암호화된 데이터 전송 → 사용자의 개인정보 보호
-> 이 과정을 통해 데이터가 보호되며, 해커가 중간에서 데이터를 탈취하더라도 암호화되어 있어 읽을 수 없음
✅ SSL 인증이 필요한 이유
📌 SSL 인증서란?
SSL(Secure Sockets Layer) 인증서는 웹사이트의 신뢰성을 보장하고 데이터를 암호화하는 역할을 한다.
- 웹사이트가 **신뢰할 수 있는 기관(CA, 인증기관)**에서 발급한 인증서를 사용하면 브라우저가 이를 확인하고 안전한 연결을 보장
- 예: DigiCert, Let's Encrypt, GlobalSign 등
📌 SSL 인증서의 주요 기능
- 데이터 암호화 → HTTPS 통신에서 데이터를 암호화하여 해킹 방지
- 서버 인증 → 사용자가 접속한 웹사이트가 신뢰할 수 있는 사이트인지 확인
- 데이터 무결성 보장 → 전송 중 데이터가 변조되지 않도록 보호
이를 통해 SSL 인증이 필요한 이류를 다음 두 가지로 정리할 수 있겠다.
- 데이터 암호화를 통해 중간자 공격(Man-in-the-Middle Attack) 방지.
- HTTPS 환경에서 CORS 오류가 발생하는 경우, SSL 인증서를 제대로 적용해야 해결됨.
다음은 SSL 인증서의 종류이다. 그냥 참고 정도만 하면 될 것 같다.
📌 SSL 인증서의 종류
인증서 종류 | 특징 | 적용 예시 |
DV (Domain Validation) | 도메인 소유자 인증만 필요 | 개인 웹사이트, 블로그 |
OV (Organization Validation) | 기업 정보 확인 후 발급 | 중소기업, 일반 회사 웹사이트 |
EV (Extended Validation) | 철저한 기업 검증 후 발급 | 금융기관, 대기업, 전자상거래 사이트 |
✅ HTTPS 환경에서의 CORS 문제
📌 HTTPS 환경에서 CORS 문제가 발생하는 이유
HTTPS 환경에서는 보안이 더욱 강화되므로 CORS 정책이 더 엄격하게 적용된다.
일반적으로 다음과 같은 이유로 HTTPS 환경에서 CORS 문제가 발생한다.
- 서버의 CORS 설정이 불완전
- 서버가 Access-Control-Allow-Origin을 적절히 설정하지 않으면 요청이 차단됨.
- 예: Access-Control-Allow-Origin: * 대신 특정 도메인만 허용하는 경우 발생
- HTTPS API가 CORS 정책을 엄격하게 적용
- 일부 API는 HTTP 요청을 허용하지 않으며, HTTPS에서만 접근 가능
- HTTPS 요청에 대한 Access-Control-Allow-Origin 미설정
- 예: 서버가 HTTP 요청만 허용하고, HTTPS 요청은 차단할 경우
- 서버에서 Access-Control-Allow-Credentials 미설정
- credentials: include 옵션을 사용하여 쿠키, 인증 토큰을 포함할 때,
서버가 Access-Control-Allow-Credentials: true를 설정하지 않으면 차단됨.
- credentials: include 옵션을 사용하여 쿠키, 인증 토큰을 포함할 때,
✅ Mixed Content 오류란?
📌 Mixed Content 오류의 원인
- 웹사이트가 HTTPS로 운영되는 경우, 페이지 내의 모든 리소스(이미지, 스크립트, 스타일시트)도 HTTPS로 로드해야 한다.
- 하지만 HTTP 리소스를 포함하면 Mixed Content 오류 발생
- 예:
<img src="http://example.com/image.jpg"> <!-- ❌ 오류 발생 -->
<img src="https://example.com/image.jpg"> <!-- ✅ 정상 동작 -->
📌 Mixed Content 오류 해결 방법
🔹 1. 모든 리소스를 HTTPS로 변경
- API, 이미지, CSS, JS 파일 등 모든 외부 리소스를 HTTPS URL로 변경
- http://example.com → https://example.com
🔹 2. CSP(Content Security Policy) 설정
- CSP를 이용하면 혼합된 콘텐츠를 차단할 수 있다.
- meta 태그 또는 HTTP 응답 헤더에서 설정 가능
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
🔹 3. 자동 업그레이드 기능 사용
- 최신 브라우저는 upgrade-insecure-requests 정책을 지원하여 자동으로 HTTP 요청을 HTTPS로 업그레이드함.
🔹 4. 브라우저 개발자 도구(DevTools)에서 Mixed Content 오류 확인
- Chrome DevTools (F12 → Console)
- Mixed Content: The page was loaded over HTTPS, but requested an insecure resource 경고 메시지 확인
- 해당 리소스를 HTTPS로 변경
✅ HTTP → HTTPS 강제 리다이렉트 설정
HTTP 요청이 들어오면 자동으로 HTTPS로 리다이렉트되도록 설정할 수 있다.
🔹 1. .htaccess 설정 (Apache)
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
🔹 2. Nginx 설정
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
🔹 3. React / Vite 프로젝트에서 환경 변수 설정
vite.config.ts에서 HTTPS 환경을 설정할 수도 있다.
export default defineConfig({
server: {
https: true,
},
});
5. 결론
- HTTPS 환경에서 CORS 문제는 서버의 Access-Control-Allow-Origin 설정이 필요하다.
- Mixed Content 오류는 HTTP 리소스를 포함했을 때 발생하며, HTTPS로 변경해야 한다.
- upgrade-insecure-requests를 사용하면 자동으로 HTTPS로 업그레이드 가능하다.
- .htaccess 또는 Nginx 설정을 통해 HTTP → HTTPS 리디렉션을 적용할 수 있다.
🚀 HTTPS 환경에서 API 요청 및 외부 리소스를 안전하게 로드하도록 설정하는 것이 중요하다!