- 공유 링크 만들기
- X
- 이메일
- 기타 앱
브라우저 콘솔에 빨간 글씨로 "Access to fetch at ... has been blocked by CORS policy"라는 메시지가 뜬다면, 당신은 지금 웹 개발자라면 누구나 한 번쯤 마주하는 CORS 에러와 싸우고 있는 것입니다. 이 글에서는 CORS가 무엇인지, 왜 발생하는지, 그리고 실전에서 검증된 해결 방법을 프레임워크별 코드와 함께 제시합니다. Swagger UI를 사용하는 Spring Boot 개발자라면 특히 주목하십시오.
| CORS 에러 완벽 해결 가이드: 프론트엔드 개발자가 알아야 할 4가지 방법 |
CORS란 무엇인가: 브라우저의 보안 메커니즘
CORS(Cross-Origin Resource Sharing)는 브라우저가 자신의 출처(Origin)가 아닌 다른 출처의 리소스 접근을 서버가 명시적으로 허용할 때만 가능하도록 제어하는 보안 정책입니다. 여기서 '출처'란 프로토콜(https://) + 도메인(example.com) + 포트(:8080) 조합을 의미합니다.
- 동일 출처: https://example.com:443/api → https://example.com:443/users (허용)
- 교차 출처: https://example.com → http://api.example.com (차단 대상)
- 교차 출처: https://example.com:3000 → https://example.com:8080 (포트가 달라도 차단)
브라우저는 보안상의 이유로 스크립트에서 시작된 교차 출처 HTTP 요청을 기본적으로 차단합니다. 이것이 바로 동일 출처 정책(Same-Origin Policy)이며, CORS는 이 정책을 안전하게 우회할 수 있는 공식적인 방법입니다.
CORS 에러가 발생하는 전형적인 상황
다음 시나리오에서 CORS 에러가 발생합니다:
- 프론트엔드-백엔드 분리 개발: localhost:3000(React) → localhost:8080(Spring Boot) API 호출
- 프로토콜 불일치: https://example.com에서 http://api.example.com으로 요청
- 외부 API 직접 호출: 클라이언트에서 XMLHttpRequest/fetch로 타사 API 요청
- 인증 정보 포함 요청: credentials: 'include' 옵션으로 쿠키를 포함한 요청
- 서버 응답 헤더 누락: 백엔드가 Access-Control-Allow-* 헤더를 응답에 포함하지 않음
- 허용되지 않은 메서드/헤더: PUT, DELETE 또는 커스텀 헤더(X-Custom-Header)를 사용한 요청
주의: CORS 에러는 브라우저 레벨에서 발생합니다. Postman이나 cURL에서는 정상 작동하지만 브라우저에서만 차단된다면, 이는 100% CORS 문제입니다.
실전 해결 방법: 4가지 접근법
1) 서버에서 CORS 헤더 설정 (권장)
가장 정석적이고 안전한 방법입니다. 서버 응답에 다음 헤더를 포함시킵니다:
- Access-Control-Allow-Origin: 허용할 출처 지정 (* 또는 https://example.com)
- Access-Control-Allow-Methods: GET, POST, PUT, DELETE 등 허용 메서드
- Access-Control-Allow-Headers: Content-Type, Authorization 등 허용 헤더
- Access-Control-Allow-Credentials: true (인증 정보 포함 시 필수)
Spring Boot 예제:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
}
Express.js 예제:
const cors = require('cors');
app.use(cors({
origin: 'https://example.com',
credentials: true
}));
경고: 운영 환경에서 Access-Control-Allow-Origin: *와 allowCredentials: true를 동시에 사용하면 보안 취약점이 됩니다. 반드시 특정 도메인을 명시하십시오.
2) 프록시 서버 사용
클라이언트 ↔ 프록시 ↔ 타깃 서버 구조로 프록시가 요청을 대신 전달합니다. 브라우저는 동일 출처(프록시)로 요청하므로 CORS가 발생하지 않습니다.
- 개발 환경: webpack-dev-server, Vite, CRA의 proxy 설정 활용
- 운영 환경: Nginx, Apache를 리버스 프록시로 구성
Vite 설정 예제 (vite.config.js):
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}
3) 브라우저 보안 해제 (개발 전용, 비권장)
Chrome을 보안 검사 없이 실행하여 임시로 CORS를 우회합니다. 절대 운영 환경에서 사용해서는 안 되며, 개발 중 긴급 테스트 용도로만 제한적으로 사용하십시오.
chrome.exe --disable-web-security --user-data-dir=C:\Temp\chrome_dev
4) Chrome 확장 프로그램 (개발 편의용)
"CORS Unblock", "Allow CORS" 같은 확장을 설치하여 일시적으로 CORS 정책을 우회할 수 있습니다. 마찬가지로 개발 환경에서만 사용하고, 테스트 완료 후 반드시 비활성화하십시오.
Swagger/springdoc-openapi 사용 시 특수 케이스
Swagger UI에서 API 정의를 불러올 때 CORS 에러가 발생한다면, 이는 Swagger UI가 API 스펙을 다른 출처로 인식하여 요청하기 때문입니다. 해결 방법은 간단합니다:
Default Server URL을 명시적으로 지정하면 Swagger UI가 해당 URL을 기준으로 요청을 보내 동일 출처 정책을 위배하지 않습니다.
@OpenAPIDefinition(
servers = {
@Server(url = "/", description = "Default Server URL")
}
)
public class OpenApiConfig {
}
주의: 애플리케이션에 context path나 prefix가 있다면 반드시 포함해야 합니다. 예: @Server(url = "/api/v1")
이 설정만으로도 대부분의 Swagger CORS 문제가 해결되며, 별도의 CORS 필터나 설정이 불필요합니다.
정리 및 Action Item
CORS는 브라우저의 동일 출처 정책으로 인해 발생하는 보안 메커니즘이며, 서버에서 명시적으로 허용 헤더를 설정하는 것이 가장 안전하고 권장되는 해결 방법입니다. 프록시 서버는 개발 환경에서 유용하지만, 운영에서는 Nginx 같은 리버스 프록시를 활용하십시오. 브라우저 보안 해제나 확장 프로그램은 절대 운영 환경에 적용해서는 안 됩니다.
당장 실행할 Action Item: 백엔드 코드에 CORS 설정을 추가하고, 허용 출처를 운영 도메인으로 명시적으로 지정하십시오. Spring Boot라면 WebMvcConfigurer를 구현하고, Swagger 사용 중이라면 @OpenAPIDefinition에 Default Server URL을 설정하십시오. 이 두 가지만으로도 90% 이상의 CORS 문제가 해결됩니다.
#함께 읽으면 좋은 글
HTTP vs HTTPS 차이점 완벽 분석과 SSL 인증서가 SEO 순위에 미치는 영향 : 바로보기
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기