프론트엔드에서 회원가입 API 호출 시 다음과 같은 오류가 발생했다.
Access to XMLHttpRequest at 'https://api.gasdg.store/api/users/join' from origin 'https://gasdg.store' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
문제 정의
에러 원인을 찾아보니
프론트엔드는 https://gasdg.store, 백엔드는 https://api.gasdg.store로 도메인이 달랐다.
브라우저 보안 정책(CORS)상, 도메인/포트/프로토콜이 다르면 Cross-Origin 요청으로 간주하여 에러를 발생시킨 것이었다.
서버가 CORS를 허용하지 않으면 브라우저는 OPTIONS 프리플라이트 요청에서 차단한다.
시도했던 방법
1. @CrossOrigin 컨트롤러에 직접 선언: 컨트롤러나 메서드 단에서 설정 가능하지만, 전체 API에 일괄 적용하기 어렵고 복잡한 경우 한계가 있다.
2. WebMvcConfigurer에서 전역 CORS 설정: Spring MVC만 사용하는 경우엔 유효하나, Spring Security가 활성화된 경우 무시될 수 있다.
해결방법
Spring Security 설정 내부에서 CorsConfigurationSource Bean 등록 + 환경 변수 기반 도메인 설정
- 백엔드 -
applicaiton.yml
CORS_ALLOWED_ORIGINS: https://gasdg.store
환경변수로 관리하여 운영/개발 환경별로 다른 Origin 허용 가능하게 했다.
SecurityConfig.java
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
String allowedOrigins = env.getProperty("CORS_ALLOWED_ORIGINS", "http://localhost:5173");
config.setAllowedOrigins(Arrays.asList(allowedOrigins.split(",")));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setExposedHeaders(List.of("Authorization"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config); // 모든 경로에 허용
return source;
}
setAllowedOrigins() | 허용할 Origin을 지정 |
setAllowedMethods() | 허용할 HTTP 메서드 (OPTIONS 포함해야 함) |
setAllowedHeaders() | 클라이언트 요청에서 사용할 수 있는 헤더 |
setExposedHeaders() | 응답 시 노출할 헤더 (예: Authorization) |
setAllowCredentials(true) | 쿠키/인증정보 포함 여부 허용 |
setMaxAge(3600L) | 프리플라이트 응답 캐싱 시간 (초) |
SecurityFilterChain
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
// ... 생략
- http.cors()를 사용해야 Spring Security가 CorsConfigurationSource를 적용한다.
- WebMvcConfigurer에서 CORS를 설정하더라도, Security가 필터단에서 요청을 막기 때문에 적용되지 않을 수 있다.
- 프론트엔드 -
vite.config.js
export default defineConfig({
base: "/",
plugins: [react()],
server: {
proxy: {
"/api": {
target: "https://api.gasdg.store",
changeOrigin: true,
secure: false,
},
},
},
});
- 개발 환경에서 Cross-Origin 요청을 프록시로 우회한다.
- 실제 서버에서는 프록시 없이 직접 요청하므로 서버 측 CORS 설정이 필수이다.
핵심 키워드
- CORS: 다른 Origin 간 요청 제한하는 브라우저 보안 정책
- CorsConfigurationSource: Spring에서 CORS 정책을 설정하는 Bean
- SecurityFilterChain: Spring Security의 보안 필터 체인
- HttpSecurity.cors(): CORS 설정을 Security 내부에 등록
- Vite Proxy: 개발 환경에서의 Cross-Origin 문제 해결용 우회 방식
마무리
CORS 문제는 초기에 사소해 보이지만,
실제 운영 환경에서는 인증 흐름과 직결되기 때문에 정확한 위치에서 제대로 설정하는 것이 중요하다.
- 프론트-백엔드 도메인이 분리된 구조에서는 반드시 Spring Security 레벨에서 CORS를 설정하자.
- WebMvcConfigurer만 사용하는 방식은 Security 필터 앞에서 무시될 수 있어 보안상 허점이 될 수 있다.
이 설정을 통해 프론트엔드와 백엔드가 완전한 분리 구조를 유지하면서도 안전하게 통신할 수 있는 기반을 마련할 수 있었다.
'프로젝트 > 기업 일정 관리 웹' 카테고리의 다른 글
REST API 에서 Enum 직렬화 문제 (0) | 2025.04.02 |
---|---|
Redis 적용하기 (0) | 2025.04.02 |
도메인 별 SSL 인증서 문제 해결 (0) | 2025.04.02 |
Access Token & Refresh Token 설계와 구현 (0) | 2025.04.02 |
JWT 인증 시스템 구현 (0) | 2025.04.02 |