서로 다른 Origin에서 리소스를 공유할 수 있도록 하기 위해 내놓은 정책, 서로 다른 Origin 이라는 것은 도메인 혹은 포트가 서로 다르다는 것을 의미하므로 서로 다른 도메인 주소 사이에 데이터를 주고받을 수 있도록 하기 위한 정책
SPA가 등장하면서 클라이언트와 서버의 도메인을 따로 유지해야하는 경우가 생기고, 외부 API를 앱 내에 연동하여 사용하는 경우 앱과 외부 API의 Origin이 다르기 때문에 Same-Origin Policy에 의해 리소스를 공유할 수 없는 상황이 생기게 됨
Same-Origin Policy (동일 출처 정책)
웹 브라우저 보안을 위해 프로토콜, 호스트, 포트가 동일한 서버로만 ajax요청을 주고받을 수 있도록 한 정책
여기서 출처는 scheme(protocol), host(domain), port로 구성
예시) https://www.google.com/mapls 가 있다면 protocol은 http://, host는 www.google.com, port는 :443이다.
이 방식은 시나리오에 해당하는 상황일 때 브라우저는 요청을 한번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송
이때 브라우저가 본 요청을 보내기 전에 보내는 예비요청을 Preflight 라고 부름
이 예비 요청에는 HTTP 메소드 중 OPTIONS 메소드가 사용된다.
1. 브라우저가 서버에게 예비 요청을 보내고
option 요청과 함께 두 개의 다른 요청 헤더가 전송됨
Access- Control-request-Method: Post //실제요청의 메서드
Access-Control-Request-Headers: X-PINGOTHER, Content-Type // 실제요청의 추가헤더
Access-Control-Allow-Origin: http://foo.example 서버측 허가출처
Access-Control-Allow-Methods: POST, GET, OPTIONS // 허가 메서드
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 서버측 허가헤더
Access-Control-Max-Age: 86400 // Prefilght 응답 캐시기간
브라우저는 예비요청과 응답을 비교한 후 요청을 보내느 것이 안전하다고 판단되면 엔드 포인트로 다시 본 요청을 보내게된다
이후 서버가 이 본 요청에 대한 응답을 하면 브라우저는 최종적으로 이 응답데이터를 자바스크립트로 넘겨준다.
Simple Request(단순 요청) 예비 요청을 보내지않고 서버에게 본 요청을 보낸 후, 서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin과 같은 값을 보내주면 그때 브라우저가 cors 정책 위반 여부를 검사하는 방식
하지만 사용할 수 있는 조건이 있다.
조건
1. 요청의 메서드는 GET, HEAD, POST 중 하나여야함
2. Accept, Accept-Language, Content-Language, Cotent-type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안됨
3. Content-type를 사용한 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용
시나리오는 인증된 요청을 사용하는 방법으로 다른 출처간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법
인증관련 헤더를 포함할 때 사용하는 요청으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 기본적으로 요청에 담지않으므로, CREDENTIALS 옵션을 변경하지않고서 cookie를 주고 받을 수 없다.
fetch.(url, {credentials: 'include'})
대표적 방법으로, Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해주는 것
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*"); // 모든 도메인
res.header("Access-Control-Allow-Origin", "https://example.com"); // 특정 도메인
});
const cors = require('cors');
const app = express();
app.use(cors());
아무 옵션없이 설정하면 모든 cross-origin 요청에 대해 응답이므로, 특정 도메인이나 특정 요청에만 응답하게 옵션을 설정하는게 좋음
const options = {
origin: "http://example.com", // 접근 권한을 부여하는 도메인
credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가
optionsSuccessStatus: 200, // 응답 상태 200으로 설정
};
app.use(cors(options));
app.get("/example/:id", cors(), function (req, res, next) {
res.json({ msg: "example" });
});
webpakc-dev-server에 프록시 기능을 사용하면 cors정책을 우회가능
// 프록시 쓰지 않았을때
// localhost:8080(클라이언트 측) --X (CORS)--> domain.com (서버 측)
// 프록시를 설정 후
// localhost:8080(클라이언트 측) --O 프록시가 설정된 Webpack Dev Server--> domain.com (서버 측)
module.exports = {
devServer: {
proxy: {
"/api": {
target: "domain.com",
changeOrigin: true,
},
},
},
};
create-react-app 으로 생성한 프로젝트에서는, package.json 에 proxy 값을 설정하여 proxy 기능을 활성화 하는 방법도 있다.
{
//...
"proxy": "http://localhost:4000"
}