CORS 문제

Dear·2025년 10월 4일

TIL

목록 보기
71/74

💙 CORS

CORS(Cross-Origin Resource Sharing)는 웹 브라우저가 "다른 오리진(Origin)"에 있는 리소스에 접근하려고 할 때 보안상 제한을 두는 메커니즘이다.

💙 오리진

프로토콜과 호스트 이름, 포트의 조합을 말한다.
예를 들어 https://test.com/test1 라는 주소에서 오리진은 https://test.com 을 뜻한다.

오리진은 다음 세 가지가 모두 동일해야 같은 오리진으로 간주한다.

구성 요소예시
프로토콜http:// or https://
도메인(host)example.com, localhost, 127.0.0.1
포트 번호:3000, :8080, :5000

즉,
http://localhost:3000http://localhost:5000 은 다른 오리진이다.
-> 프로토콜과 도메인은 같지만 포트가 다르다

💙 이런 제약이 필요한 이유

CORS는 보안상의 이유로 존재한다.
만약 아무 웹사이트에서든 임의의 API 서버로 요청을 보낼 수 있다면, 악의적인 사이트가 사용자의 쿠키나 인증 토큰을 몰래 이용해서 API 호출을 할 수도 있다.
그래서 브라우저는 다른 오리진의 리소스를 요청할 때, 서버가 명시적으로 허용(CORS Header)하지 않으면 요청을 차단한다.

💙 CORS 동작 방식

프론트엔드에서 fetch('http://localhost:5000/api/data') 를 실행했을때 다음 과정을 거친다.

1. Preflight 요청 (OPTIONS)

브라우저는 먼저 OPTIONS 요청을 보내서 서버에게 "이 요청을 해도 괜찮은가?" 묻는다

2. 서버 응답 헤더 확인

서버가 다음과 같은 헤더를 포함해 응답하면 요청을 허용한다.

Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE

그러면 실제 요청이 이어서 수행된다.

3. 만약 헤더가 없다면

브라우저가 보안상 이유로 응답을 차단한다. 

콘솔 메시지 
```Access to fetch at 'http://localhost:5000/api/data' from origin 		'http://localhost:3000' 
has been blocked by CORS policy...
```

💙 프론트엔드 개발 중 CORS 에러가 자주 나는 이유

  • React/Vue 등 프론트엔드 : localhost:3000
  • Node/Django/Spring 백엔드 : localhost:8080 또는 localhost:5000

다른 포트에서 돌아가기 때문에 브라우저 입장에서는 다른 오리진으로 인식되어 CORS 에러가 발생한다.

💙 해결 방법

1. 백엔드에서 CORS 허용

Express.js

const cors = require('cors');
app.use(cors({ origin: 'http://localhost:3000' }));

Spring Boot

@CrossOrigin(origins = "http://localhost:3000")

서버가 응답 헤더에 Access-Control-Allow-Origin을 추가하도록 설정

2. 프론트엔드 프록시 서버 사용 (개발 환경용)

개발 중이라 백엔드를 직접 수정할 수 없거나, 서버 설정이 귀찮을 때 자주 사용한다.

원리
프록시 서버가 브라우저로부터 같은 오리진으로 보이는 요청을 받은 뒤, 내부적으로 백엔드 서버로 요청을 전달한다.
브라우저는 이 과정을 인지하지 못하기 때문에 CORS 제한이 발생하지 않는다

예시 (React CRA)

// package.json
"proxy": "http://localhost:5000"

// 이후 요청
fetch('/api/data')

// 실제로 전달
프론트엔드(3000) → 프록시 → 백엔드(5000)

브라우저는 localhost:3000으로 요청했다고 생각하기 때문에 CORS 에러가 안 난다.

3. Nginx 또는 Vite Dev Server 프록시 설정

React, Vue, Vite, Next.js 등에서도 dev 서버 설정으로 프록시를 걸 수 있다.

Nginx 예시

location /api {
  proxy_pass http://localhost:5000;
}

🤍 회고

스프링 부트 + html + javascript 로만 이루어진 프로젝트를 배포했는데 CORS 문제가 생겨 이번에 CORS에 관해 알아보았다.
“스프링 부트 + HTML + JS로만 구성된 단일 프로젝트인데도 CORS 문제가 발생했다”
는 건, 프론트엔드(JS 코드)가 요청하는 대상이 브라우저 입장에서 “다른 오리진”으로 인식되었기 때문이었다.

겉보기엔 “한 프로젝트”인데 오리진이 달라진 이유

/resources/static에 HTML/JS를 두고, REST API를 @RestController로 구현했다.
서버 안에서는 한 덩어리로 보이지만 브라우저 입장에서는 요청 주소가 다르면 다른 오리진이다.

HTML을 연 주소JS가 요청하는 API 주소오리진 일치 여부결과
http://localhost:8080/index.htmlhttp://localhost:8080/api/test✅ 같음CORS 문제 없음
http://127.0.0.1:8080/index.htmlhttp://localhost:8080/api/test❌ 다름(도메인 다름)CORS 에러 발생
https://myapp.comhttps://api.myapp.com❌ 다름(서브도메인 다름)CORS 에러 발생
https://myapp.comhttp://myapp.com❌ 다름(프로토콜 다름)CORS 에러 발생

즉, 한 스프링 프로젝트에서 배포했다 해도, 실제 서비스 환경에서 JS가 다른 오리진으로 요청하면 브라우저는 CORS를 검사한다.

해결 방법 (스프링 부트 기준)

// 전역 설정 (Spring Security나 MVC Config)
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("https://www.myapp.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true);
    }
}

// Controller 단위 설정
@CrossOrigin(origins = "https://www.myapp.com")
@RestController
public class MyController {
    // ...
}
profile
친애하는 개발자

0개의 댓글