교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다. -MDN
여기서 origin(출처) 이란 scheme(protocol), host(domain), port 로 구성된다. 예를들어, https://www.google.com/maps 라는 주소가 있다고하면
protocol은 https:// Host는 www.google.com Port는 :443 이며, 동일 출처(Same Origin) 란 scheme, host, port 가 모두 같을때를 말한다.
또한 SOP(Same-Origin Policy) 이란 같은 출처에서만 리소스를 공유할 수 있다는 규칙이다. 브라우저에서 다른 서버에서 요청할 경우에 해당되고, 브라우저를 거치지 않고 서버 간 통신을 할 때는 이 정책이 적용되지 않는다.
그런데 이런 정책이 왜 존재할까? 만약 다른 출처의 어플리케이션이 서로 통신하는 것에 대해 아무런 제약도 존재하지 않는다면 악의를 가진 사용자가 소스 코드를 보고 CSRF(Cross-Site Request Forgery)나 XSS(Cross-Site Scripting)와 같은 방법을 사용하여 정보를 탈취할 수 있다.
CORS는 다른 출처의 리소스가 필요한 경우, SOP를 우회하기 위한 여러가지 방법 중 가장 권장되는 방법이다.
Preflight Request는 요청을 예비 요청과 본 요청으로 나눈다. OPTIONS 메서드를 통해 다른 도메인의 리소스에 요청이 가능한지 (실제 요청이 전송하기에 안전한지) 확인 작업을 하고, 요청이 가능하다면 실제 요청을 보낸다. Cross-origin 요청은 유저 데이터에 영향을 줄 수 있기 때문에 Preflight 요청을 한다.
OPTIONS 요청과 함께 두 개의 다른 요청 헤더가 전송된다. 아래에서 첫 행은 실제 요청을 전송할 때 POST 메서드로 전송된다는 것이고, 두번째 행은 실제 요청을 전송 할 때 X-PINGOTHER 와 Content-Type 사용자 정의 헤더와 함께 전송된다는 것을 서버에 알려준다.
Access-Control-Request-Method: POST # 실제요청의 메서드
Access-Control-Request-Headers: X-PINGOTHER, Content-Type # 실제요청의 추가헤더
서버가 메서드와 헤더를 받을 수 있음을 알려준다. 마지막행은 preflight request에 대한 응답을 캐시할 수 있는 시간(초)이다.
프리플라이트를 보내면 사전, 실제 요청 두번이 매번 왔다갔다 하므로 브라우저가 캐싱을 해두고 똑같은 요청을 보낼때, 사전 요청을 보내지 않고 바로 본 요청을 보낸다.
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는 Preflight Request와 다르게 요청을 보내면서 즉시 cross origin인지 확인하는데, 다음 조건을 모두 충족해야한다.
인증 관련 헤더를 포함할 때 사용하는 요청이다. 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 기본적으로 요청에 담지 않으므로, credentials 옵션을 변경하지 않고서는 cookie를 주고 받을 수 없다.
옵션은 세가지가 있다.
axios 로 통신할 시, withCredentials 설정을 true 로 넣어주면 된다.
axios.post(주소, 데이터, { withCredentials: true });
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());
module.exports = {
devServer: {
proxy: {
"/api": {
target: "domain.com",
changeOrigin: true,
},
},
},
};
{
//...
"proxy": "http://localhost:4000"
}
Credentials 이란 쿠키, Authorization 인증 헤더, TLS client certificates(증명서)를 내포하는 자격 인증 정보를 말한다.
기본적으로 브라우저가 제공하는 요청 API 들은 별도의 옵션 없이 브라우저의 쿠키와 같은 인증과 관련된 데이터를 함부로 요청 데이터에 담지 않도록 되어있다. 이는 응답을 받을때도 마찬가지이다. 따라서 요청과 응답에 쿠키를 허용하고 싶을 경우, 이를 해결하기 위한 옵션이 바로 withCredentials 옵션이다.
withCredentials 옵션은 단어 그대로, 다른 도메인(Cross Origin)에 요청을 보낼 때 요청에 인증(credential) 정보를 담아서 보낼 지를 결정하는 항목이다. 즉, 쿠키나 인증 헤더 정보를 포함시켜 요청하고 싶다면, 클라이언트에서 API 요청 메소드를 보낼때 withCredentials 옵션을 true로 설정해야한다. 또한 인증된 요청을 정상적으로 수행하기 위해선 클라이언트 뿐만 아니라 서버에서도 Access-Control-Allow-Credentials 헤더를 true로 함으로써 인증 옵션을 설정해주어야 한다.
정리하자면 클라이언트나 서버나 둘다 Credentials 부분을 true로 설정해줘야 한다는 말이다.
표준 CORS요청은 기본적으로 쿠키를 설정하거나 보낼 수 없다.
프론트에서 ajax 요청할 때, withCredentials부분을 true로 해서 수동으로 CORS 요청에 쿠키값을 넣어줘야 한다.
마찬가지로 서버도 응답헤더에 Access-Control-Allow-Credentials를 true로 설정해야 한다.