CORS 즉, 교차 "출처" 리로스 공유라고 불리는 개념인데 "출처" 보단 "리소스" 느낌 그대로 살리겠다.
CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)는 웹 페이지가 다른 도메인의 리소스에 접근할 수 있도록 허용하는 메커니즘이다.
즉, 한 리소스에서 다른 리소스 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.
CORS는 추가 HTTP 헤더를 사용하여 브라우저와 서버가 서로 어떤 종류의 교차 도메인 요청을 허용할지 협상할 수 있게 해준다.
즉, 웹 앱에서는 자신의 리소스(도메인, 프로토콜, 포트)와 다를 때 CORS HTTP 요청을 실행한다.
위 사진은 https://domain-a.com
의 프론트 엔드 JavaScript 코드가 XMLHttpRequest를 사용하여 https://domain-b.com/data.json
을 요청하는 경우이다.
이때 보안 상의 이유로, 브라우저는 스크립트에서 시작한 CORS HTTP 요청을 제한한다.
예를 들어, XMLHttpRequest와 Fetch API는 동일 출처 정책을 따르는데, 이 API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있으며, 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 한다.
기본적으로 웹 브라우저는 같은 출처 정책(Same-Origin Policy)을 따르기 때문에, 웹 페이지는 동일한 도메인의 리소스만 불러올 수 있다고 하였다. 그러나 현대의 웹 어플리케이션들은 다양한 출처의 리소스를 필요로 하는 경우가 많으며 이때 CORS 정책이 활용된다.
CORS 체제는 브라우저와 서버 간의 안전한 교차 출처 요청 및 데이터 전송을 지원하고 최신 브라우저는 XMLHttpRequest 또는 Fetch와 같은 API에서 CORS를 사용하여 교차 출처 HTTP 요청의 위험을 완화한다.
단순 요청은 특정 조건(예: GET, POST, HEAD 방식의 요청, 일부 헤더만을 사용하는 경우 등)을 만족하는 경우에 해당한다.
브라우저는 자동으로 Origin 헤더를 요청에 포함시켜 서버에 보내는데 이때 서버는 Access-Control-Allow-Origin 헤더를 응답에 포함시켜 특정 출처(또는 모든 출처)가 자원에 접근할 수 있음을 나타낸다.
"비단순" 요청은 서버에 인증 정보를 포함하거나, PUT, DELETE 방식의 HTTP 메소드를 사용하거나, 사용자 정의 헤더를 사용하는 경우에 해당한다.
브라우저는 실제 요청을 보내기 전에 OPTIONS 메소드를 사용하여 사전 요청을 보는데 이 요청은 서버에게 실제 요청을 안전하게 처리할 수 있는지 묻는다.
서버는 Access-Control-Allow-Methods, Access-Control-Allow-Headers 등의 CORS 관련 헤더를 통해 어떤 요청 메소드와 헤더, 출처를 허용할지 응답한다.
브라우저는 서버의 응답을 확인한 후 실제 요청을 보내거나 차단한다.
CORS 요청은 기본적으로 쿠키나 HTTP 인증 정보와 같은 자격증명을 포함하지 않는다.
자격증명을 포함시키기 위해서는 요청하는 쪽( front-end 개발자 )에서 XMLHttpRequest나 Fetch API를 사용할 때 withCredentials 속성을 true로 설정해야 한다.
// fetch API
fetch("https://example.com:1234/users/login", {
method: "POST",
credentials: "include", // 클라이언트와 서버가 통신할때 쿠키 값을 공유하겠다는 설정
})
// axios
// 2. axios 옵션 객체로 넣기
axios.post(
'https://example.com:1234/users/login',
{ profile: { username: username, password: password } },
{ withCredentials: true }
).then(response => {
console.log(response);
console.log(response.data);
})
일반적으로 React 개발 서버는 기본적으로 3000번 포트에서 실행되고, Spring 개발 서버는 8080번 포트에서 실행되는 경우가 많다.
이런 경우 두 서버는 다른 포트에서 실행되고 있기 때문에, 브라우저의 같은 출처 정책(Same-Origin Policy)에 따라 서로 다른 출처로 간주된다.
예를 들어, React 애플리케이션이 localhost:3000에서 실행 중이고, Spring 백엔드 서버가 localhost:8080에서 실행 중일 때, React 애플리케이션에서 Spring 서버로 API 요청을 보내면 브라우저는 이를 교차 출처 요청으로 인식하고 CORS 정책을 적용한다.
따라서, 이러한 환경에서 개발할 때는 몇 가지 방법으로 CORS 문제를 해결할 수 있다
Spring 서버의 CORS 설정을 변경하여 특정 출처(예: localhost:3000) 또는 모든 출처로부터 오는 요청을 허용한다.
서버 측에서는 Access-Control-Allow-Credentials 헤더를 true로 설정하여 자격증명을 포함한 요청을 허용할 수 있다.
CORS는 보안과 유연성을 제공하지만, 잘못 구성되었을 때는 보안 취약점이 될 수 있다. 따라서 CORS 정책을 설정할 때는 최소 권한 원칙을 따르는 것이 중요하다.
예를 들어, " Access-Control-Allow-Origin 헤더에 *를 사용하여 모든 출처를 허용하면 된다. " 에서 서버에서 응답 헤더를 설정하는데 있어 몇가지 제약이 있어 주의하여야 한다.
Spring Boot에서 특정 출처, 예를 들어 localhost:3000을 허용하려면, CORS 설정을 커스텀할 수 있는 여러 방법이 있다.
특정 컨트롤러 또는 핸들러 메서드에 @CrossOrigin 어노테이션을 사용하여 CORS 설정을 할 수 있다.
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ExampleController {
@CrossOrigin(origins = "http://localhost:3000")
@GetMapping("/example")
public String example() {
return "Allowed CORS for localhost:3000";
}
}
WebMvcConfigurer 인터페이스를 구현하거나 WebMvcConfigurerAdapter를 확장하여 전역 CORS 설정을 추가할 수 있다.
import org.springframework.context.annotation.Bean;
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("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD")
.allowCredentials(true);
}
}
React 개발 서버의 프록시 기능을 사용하여 API 요청을 React 서버가 아닌 Spring 서버로 전달하도록 설정할 수 있다.
이렇게 하면, 모든 API 요청이 동일 출처로부터 보내진 것으로 간주되어 CORS 문제를 회피할 수 있다.
React 프로젝트를 create-react-app으로 생성했다면, package.json 파일에 프록시 설정을 추가할 수 있다.
{
"name": "your-app",
"version": "0.1.0",
"private": true,
// ... 기타 설정들
"proxy": "http://localhost:8080"
}
이 설정에 따라, React 개발 서버는 자동으로 8080 포트로 가는 API 요청을 프록시한다. 이 방법은 "단순 요청" 에만 적용되며, 사전 요청(Preflight Request)에는 적용되지 않을 수 있다.
또한, fetch나 axios 등을 사용할 때 전체 URL을 작성하지 않고 경로만 지정하면 된다. (예: /api/users).
React 개발 서버가 아닌 다른 프록시 설정이 필요하다면, src 폴더 내에 setupProxy.js 파일을 생성하고 http-proxy-middleware
라이브러리를 사용하여 보다 복잡한 프록시 설정을 할 수 있다.
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8080',
changeOrigin: true
})
);
};
Vue CLI를 사용하여 생성된 Vue.js 프로젝트에서는 vue.config.js 파일을 사용하여 프록시 설정을 할 수 있다.
module.exports = {
// ... 기타 설정들
devServer: {
proxy: {
// 모든 `/api` 요청을 `localhost:8080`으로 프록시
'/api': {
target: 'http://localhost:8080',
ws: true,
changeOrigin: true
},
// 다른 경로나 설정이 필요하다면 추가
}
}
};
위 설정은 개발 서버가 /api로 시작하는 모든 요청을 http://localhost:8080으로 전달하도록 지시한다.
이러한 프록시 설정은 개발 환경에서 CORS 이슈를 해결하는 간편한 방법을 제공한다.
하지만 실제 프로덕션 환경에서는 서버와 클라이언트가 같은 도메인과 포트를 사용하도록 배포하거나, CORS 정책을 올바르게 구성해야 한다.
개발 중에만 사용할 목적으로 CORS를 우회하는 브라우저 확장 프로그램을 사용할 수도 있다. 하지만 이 방법은 보안 문제를 일으킬 수 있으므로 개발 환경에서만 사용해야 한다.