CORS (Cross-Origin Resource Sharing) 완전 정복
1. CORS란?
- *CORS(Cross-Origin Resource Sharing)**는 직역하면 ‘교차 출처 리소스 공유 정책’
웹 생태계에서는 보안을 위해 기본적으로 다른 출처의 리소스를 제한하는데, CORS는 특정 조건 하에 이를 허용해주는 정책을 말함. 여기서 핵심은 ‘출처(Origin)’
Warning! 출처(Origin)인 Protocol + Hostname + Port를 비교하여 하나라도 다르면 브라우저는 이를 차단하고 아래와 같은 에러를 만남
Access to fetch at ‘...’ from origin ‘...’ has been blocked by CORS policy...
2. 출처 (Origin) 이해하기
URL의 구성 요소
URL은 다음과 같이 구성됨.
- Protocol (Scheme):
http,https - Host: 사이트 도메인 (예:
www.domain.com) - Port: 포트 번호 (예:
3000,8080) - Path: 내부 경로
- Query String: 요청의 key-value
- Fragment: 해시 태그
동일 출처(Same-Origin)의 기준
브라우저는 Protocol, Host, Port 이 3가지가 모두 일치해야 ‘같은 출처’로 인식함. 하나라도 다르면 **다른 출처(Cross-Origin)**로 간주하여 차단
비교 예시
기준 URL: https://www.myshop.com (기본 포트 443)
| URL | 접근 가능 여부 (SOP 준수) | 이유 |
|---|---|---|
https://www.myshop.com/user | ✅ | 프로토콜, 호스트, 포트 일치 |
https://myshop.com/user | ✅ | (서브 도메인 정책에 따라 다를 수 있으나 통상 일치로 봄) |
http://www.myshop.com/user | ❌ | 프로토콜 불일치 (https vs http) |
https://en.myshop.com/user | ❌ | 호스트 불일치 (도메인 다름) |
https://www.myshop.com:8080 | ❌ | 포트 불일치 (443 vs 8080) |
참고: 출처 비교와 차단은 **‘브라우저’**가 수행함. 서버는 정상적으로 200 OK를 응답하더라도, 브라우저가 출처를 비교해 다르면 데이터를 버리고 에러를 띄움 (Server-to-Server 통신에서는 발생하지 않음)
3. 동일 출처 정책 (SOP: Same-Origin Policy)
SOP는 “동일한 출처에 대해서만 리소스를 공유할 수 있다”는 보안 규칙 같은 출처의 리소스는 자유롭게 가져오지만, 다른 출처의 리소스(이미지, API 등)는 상호작용을 막는 것이 기본 원칙
SOP가 필요한 이유
출처가 다른 애플리케이션끼리 자유롭게 통신하면 보안에 매우 취약해짐
- CSRF (Cross-Site Request Forgery): 사용자가 악성 사이트에 접속했을 때, 해커가 심어둔 코드가 사용자의 브라우저를 통해 로그인이 되어 있는 다른 사이트(은행, 포털 등)에 요청을 보내 개인정보를 탈취하거나 조작할 수 있음
- XSS (Cross-Site Scripting): 악성 스크립트 실행 방지
즉, SOP는 악의적인 스크립트가 실행되지 않도록 브라우저 차원에서 사전에 방지하는 안전장치임. 하지만 현대 웹에서는 외부 리소스 사용이 필수적이므로, CORS 정책을 지킨 요청에 한해 예외적으로 허용해주는 것
4. CORS 동작 원리 (기본 흐름)
기본적으로 클라이언트와 서버가 헤더를 주고받으며 확인
- 클라이언트: 요청 헤더의
Origin필드에 현재 출처를 담아 보냄 - 서버: 응답 헤더의
Access-Control-Allow-Origin필드에 리소스 접근이 허용된 출처를 담아 보냄 - 브라우저: 자신이 보낸
Origin과 서버가 준Access-Control-Allow-Origin을 비교. 유효하면 통과, 아니면 폐기(CORS 에러)
5. CORS 작동 시나리오 3가지
① 예비 요청 (Preflight Request)
가장 일반적인 시나리오. 브라우저가 본 요청을 보내기 전에 안전한지 확인하기 위해 OPTIONS 메서드로 미리 찔러보는 것
- 과정:
- 브라우저 → 서버:
OPTIONS요청 (Origin, 사용할 메서드, 헤더 정보 포함) - 서버 → 브라우저: 허용 여부 응답 (
Access-Control-Allow-Origin등) - 브라우저: 안전하다 판단되면 실제 본 요청(Actual Request) 전송
- 브라우저 → 서버:
- 캐싱: 매번 예비 요청을 보내면 성능이 저하되므로
Access-Control-Max-Age헤더로 결과를 일정 시간 캐싱할 수 있음
② 단순 요청 (Simple Request)
예비 요청 없이 바로 본 요청을 보내는 방식. 하지만 조건이 매우 까다로워 잘 쓰이지 않음
- 조건:
- 메서드:
GET,POST,HEAD중 하나 - 헤더:
Accept,Content-Type등 기본 헤더만 허용 - Content-Type:
application/x-www-form-urlencoded,multipart/form-data,text/plain만 가능. (application/json은 불가능하므로 대부분 Preflight를 탐)
- 메서드:
③ 인증된 요청 (Credentialed Request)
클라이언트가 쿠키, 토큰 등 인증 정보를 포함해 요청을 보낼 때 사용함. 설정이 더 엄격
- 클라이언트 설정:
fetch,axios,jQuery등에서credentials: 'include'또는withCredentials: true설정 필수
- 서버 설정 (중요):
Access-Control-Allow-Credentials: true로 설정해야 함Access-Control-Allow-Origin에 와일드카드()를 쓸 수 없음. 반드시 구체적인 출처(예:http://localhost:3000)를 명시해야 함
6. CORS 해결 방법 (솔루션)
① 서버에서 헤더 설정 (백엔드)
가장 정석적인 방법. 백엔드에서 허용할 출처를 명시
Spring Framework (Global 설정)
@Configurationpublic class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:8080") // 허용할 출처 .allowedMethods("GET", "POST") // 허용할 메서드 .allowCredentials(true) // 인증 정보 허용 .maxAge(3000); // Preflight 캐싱 시간 }}Spring Framework (Controller 별 설정)
@CrossOrigin(origins = "*", methods = RequestMethod.GET)@GetMapping("/url")public ResponseEntity<?> findAll() { ... }Nginx 설정
location /api/ { # CORS 헤더 추가 add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Credentials' 'true' always;
# Preflight 요청 처리 if ($request_method = OPTIONS) { return 204; }}② 프록시(Proxy) 서버 이용
브라우저의 CORS 정책은 브라우저와 서버 간의 통신에서만 적용됨. 서버와 서버 간 통신에는 적용되지 않는 점을 이용
Webpack Dev Server (React 개발 환경) 개발 중에는 로컬 프록시를 띄워 CORS를 우회할 수 있음. 브라우저는 같은 출처인 로컬 프록시로 요청을 보내고, 프록시가 실제 백엔드로 요청을 전달하는 방식
-
package.json설정 예시:{"proxy": "https://myshop.com:8080"}
주의: React의 Proxy 설정은 개발 환경(Development)에서만 동작함. 배포 시에는 백엔드나 Nginx 등에서 실제 CORS 설정을 해줘야 함