CORS(Cross-Origin Resource Sharing) 에러
어떻게 어떻게 서버 구성을 하고.. (클라우드 업로드는 일단 나중에.. )
간단하게 개발을 들어갔다.
처음 해보는 리액트 + Spring 조합 개발이기에 간단한 게시판 만들기 부터 해보자는 생각이 들었다.
일단은 대강 대충 초반 화면 만들어두고 (쳇지피티 짱... )
"자유게시판" 항목을 만들고 해당 링크에서 게시판을 만들기로 하였다.
조회를 만들고 등록을 만들던 차에 ... 생소한 에러가 발생했다.
Cors 에러라고는 적혀있는데.. 본적이 없는 에러였다. 그래서 이게 뭔지 알아보기로 하였다.
CORS(Cross-Origin Resource Sharing) 에러란?
웹 브라우저에서 보안상의 이유로 발생하는 정책 제한이다. 서버와 클라이언트 간 서로 다른 출처(origin) 에서 리소스를 요청할 때 발생한다.
CORS의 기본개념
1. 출처(Origin)란 ?
- 출처는 프로토콜(http/https), 도메인 이름(ex .www.google.com), 그리고 포트번호 (8080) 로 구성된다
예시
http://example.com:3000
https://example.com
#이 두 URL은 서로 출처가 다르다.
2. CORS가 왜 필요할까?
- 웹 브라우저는 동일 출처 정책(Same-Origin Policy) 를 기본적으로 적용한다. 악의적인 요청으로 부터 데이터를 보호해야하기 때문.
- 동일 출처 정책은 웹 페이지에서 다른 출처로의 요청을 기본적으로 차단한다.
3. CORS 요청의 유형
- 클라이언트(브라우저)가 서버에 데이터를 요청할 때 출처가 다르면 브라우저는 요청 전에 "이 출처의 요청을 허용해도 되는지" 를 서버에 확인한다.
- 만약 서버가 확인하지 않으면 브라우저는 요청을 차단하고 CORS 에러를 발생시키는것 .
CORS 에러가 발생하는 이유
클라이언와 서버가 다음 중 하나라도 다르면 CORS 에러가 발생할 수 있다.
1. 도메인 다름 : 예) http://localhost <> http://127.0.0.1
2. 프로토콜 : 예) http:// <> https://
3. 포트 : 예) http://localhost:3000 <> http://localhost:8080
지금의 경우는
프론트 서버 (리액트) : http://localhost:3000
백엔드 서버 (스프링) : http://localhost:8080
두개가 다르므로 발생하는 것이다.
그렇다면 왜 조회(GET)는 되고 , 등록(POST)에서는 CORS 에러가 발생할까?
1. GET 요청 (조회)는 간단 요청(Simple Request)
브라우저는 CORS 정책에 따라 "안전한 요청"으로 간주되는 간단 요청(Simple Request)에 대해서는 CORS 프리플라이트(preflight) 요청 없이 서버에 바로 요청을 보낸다. GET 요청은 기본적으로 간단 요청에 해당하기 때문이다.
간단 요청의 조건 :
- HTTP 메서드가 GET, HEAD, 또는 POST 이어야함
- 요청 헤더가 아래 목록에 한정 되어야 한다.
- Accept
- Accept-Language
- Content-Language
- content-Type ( 단 , application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나이어야 함)
2. POST 요청은 프리플리라이트(Preflight) 요청 필요
POST 요청은 일반적으로 요청 본문(body)을 포함하며 , 사용자 데이터를 서버로 전송하기 위해 추가 헤더(Content-Type: application/json)를 사용한다. 이 경우 브라우저는 프리플라이트 요청(OPTIONS 메서드)을 통해 서버가 이 요청을 허용하는지 먼저 확인한다.
프리플라이트 요청 조건 :
- 요청 메서드가 GET, HEAD, POST가 아님
- 요청 헤더에 표준 이외의 커스텀 헤더가 포함(Authorization, Content-Type: application/json 등)
- 요청 본문에 JSON 데이터와 같은 복잡한 내용이 포함된 경우
프리플라이트 요청의 작동 방식
1. 클라이언트에서 프리플라이트 요청 전송
- 브라우저는 HTTP OPTIONS 메서드를 사용하며 , 서버에 이 요청을 허용할 것을 묻는다.
- 프리플라이트 요청에 포함된 헤더
OPTIONS /api/resource HTTP/1.1
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
- 주요 헤더
- Origin : 요청을 보낸 출처
- Access-Control-Request-Method: 클라이언트가 요청에서 사용할 HTTP 메서드
- Access-Control-Request-Headers: 요청에 포함될 사용자 정의 헤더 목록
2. 서버의 응답
- 서버는 브라우저가 보낸 프리플라이트 요청을 처리하고 요청이 허용되는지 응답한다.
- 응답 예시
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
- 주요 헤더
- Access-Control-Allow-Origin: 요청을 허용할 출처
- Access-Control-Allow-Methods: 허용되는 HTTP 메서드
- Access-Control-Allow-Headers: 요청에서 허용되는 사용자 정의 헤더
3. 클라이언트가 본 요청을 전송
- 프리플라이트 요청이 성공하면 브라우저는 원래 요청 (POST, PUT 등.. ) 을 서버로 보낸다.
CORS 에러 해결 방법
1. Spring Boot 에서 CORS 허용 설정
Spring Boot는 기본적으로 CORS 요청을 차단한다. 이를 허용하려면 몇가지 설정을 추가해야 한다.
방법 1 : 컨트롤러에 CORS 허용 설정
@CrossOrigin 어노테이션을 사용하여 특정 컨트롤러 또는 메서드에 CORS 를 허용한다.
@CrossOrigin(origins = "허용할 도메인", alloweHeaders = "허용할 헤더")
둘다 * 를 사용하면 모든 도메인 및 헤더를 허용한다.
방법 2 : Spring Security 에서 CORS 설정 (Global 설정)
Spring Security를 사용하는 경우 , WebSecurityConfigurerAdapter 또는 SecurityFilterChain 에서 글로벌 CORS 설정을 추가한다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*"); // 모든 도메인 허용
config.addAllowedMethod("*"); // 모든 HTTP 메서드 허용 (GET, POST, PUT 등)
config.addAllowedHeader("*"); // 모든 헤더 허용
config.setAllowCredentials(true); // 쿠키 인증 허용 (필요시)
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config); // 모든 경로에 대해 CORS 설정 적용
return new CorsFilter(source);
}
}
방법 3: WebMvcConfigurer 사용
Spring Security 를 사용하지 않거나, 컨트롤러 전체에서 CORS를 설정하려면 WebMvcConfigurer를 사용한다.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 모든 경로에 대해 CORS 허용
.allowedOrigins("*") // 모든 도메인 허용
.allowedMethods("*") // 모든 HTTP 메서드 허용
.allowedHeaders("*"); // 모든 헤더 허용
}
}
클라이언트 측에서의 설정
Spring Boot에서도 서버의 CORS 설정을 올바르게 적용했음에도 문제가 계속된다면.. 클라이언트 측에서 헤더를 명확히 설정해야 할 수도 있다.
리액트 예제
import axios from "axios";
const postData = async () => {
try {
const response = await axios.post("http://localhost:8080/api/forum/posts", {
title: "게시글 제목",
content: "게시글 내용",
}, {
headers: {
"Content-Type": "application/json",
},
});
console.log(response.data);
} catch (error) {
console.error("CORS 에러:", error);
}
};
postData();