CORS란?
- 교차 출처 리소스 공유 (Cross-origin resource sharing)의 약자입니다.
- 현재 브라우저로 접속중인 페이지에서 자바스크립트를 이용해 다른 도메인 또는 포트를 가진 주소로 요청을 하는 경우, 해당 리소스에 접근을 허용했는지 확인해 보안을 높이는 동작을 CORS라고 부릅니다.
CORS 동작 방식
1. 프리플라이트 요청 (Preflight Request)
"preflighted" request는 위에서 논의한 “simple requests” 와는 달리, 먼저 OPTIONS 메서드를 통해 다른 도메인의 리소스로 HTTP 요청을 보내 실제 요청이 전송하기에 안전한지 확인합니다. Cross-site 요청은 유저 데이터에 영향을 줄 수 있기 때문에 이와같이 미리 전송(preflighted)합니다.
- Preflight Request
OPTIONS 요청과 함께 두 개의 다른 요청 헤더가 전송됩니다. 아래에서 첫 행은 실제 요청을 전송할 때 POST 메서드로 전송된다는 것이고, 두번째 행은 실제 요청을 전송 할 때 X-PINGOTHER 와 Content-Type 사용자 정의 헤더와 함께 전송된다는 것을 서버에 알려줍니다.
Access-Control-Request-Method: POST # 실제요청의 메서드
Access-Control-Request-Headers: X-PINGOTHER, Content-Type # 실제요청의 추가헤더
- Preflight Response
서버가 메서드와 헤더를 받을 수 있음을 알려줍니다. 마지막행은 preflight request에 대한 응답을 캐시할 수 있는 시간(초)입니다. 아래에서는 86400초 (=24시간)
프리플라이트를 보내면 사전, 실제 요청 두번이 매번 왔다갔다 하므로 브라우저가 캐싱을 해두고 똑같은 요청을 보낼때, 사전 요청을 보내지 않고 바로 본 요청을 보냅니다.
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 응답 캐시기간
preflight request가 완료되면 실제 요청을 전송합니다.
2. 단순 요청 (Simple Request)
Simple Request는 Preflight Request와 다르게 요청을 보내면서 즉시 cross origin인지 확인하는데, 다음 조건을 모두 충족해야합니다.
- 메서드는 GET POST HEAD 중 하나
- 헤더는 Accept, Accept-Language, Content-Language, Content-Type 만 허용
- Content-Type 헤더는 다음의 값들만 허용
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
3.인증정보 포함 요청 (Credentialed Request)
인증 관련 헤더를 포함할 때 사용하는 요청입니다. 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 기본적으로 요청에 담지 않으므로, credentials 옵션을 변경하지 않고서는 cookie를 주고 받을 수 없습니다.
옵션은 세가지가 있습니다.
- omit : 절대로 cookie 들을 전송하거나 받지 않습니다.
- same-origin : 동일 출처(same origin)이라면, user credentials (cookies, basic http auth 등..)을 전송합니다. (default 값)
- include : cross-origin 호출이라 할지라도 언제나 user credentials (cookies, basic http auth 등..)을 전송합니다.
예시)
fetch('주소', {
credentials: 'include', // 모든 요청에 인증 정보 포함
});
axios 로 통신할 시, withCredentials 설정을 true 로 넣어주면 됩니다.
axios.post(주소, 데이터, { withCredentials: true });
// 또는 공통으로 추가
axios.defaults.withCredentials = true;
또한 credentials 설정을 include/true 로 설정하면 CORS정책에 의해 Access-Control-Allow-Origin을 모든 출처를 허용하는 '*' 로 지정할 수 없다는 에러가 발생하며, 따라서 cors 설정에서 *을 입력하여 모든 출처를 허용한 경우에는 특정 출처를 정확히 명시해야 합니다.
CORS 해결 방법
Access-Control-Allow-Origin 응답 헤더 세팅
- 서버측 응답에서 접근 권한을 주는 헤더를 추가하여 해결
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*"); // 모든 도메인
res.header("Access-Control-Allow-Origin", "https://example.com"); // 특정 도메인
});
cors 모듈 사용
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" });
});
webpack-dev-server proxy 기능
- 리액트 개발환경에서, 서버쪽 코드를 수정하지 않고 해결할 수도 있습니다. 아래와 같이 프록시 속성을 설정하면, 서버에서 해당 요청을 받아줍니다.
// 프록시 쓰지 않았을때
// localhost:8080(클라이언트 측) --X (CORS)--> domain.com (서버 측)
// 프록시를 설정 후
// localhost:8080(클라이언트 측) --O 프록시가 설정된 Webpack Dev Server--> domain.com (서버 측)
module.exports = {
devServer: {
proxy: {
"/api": {
target: "domain.com",
changeOrigin: true,
},
},
},
};
중간의 프록시 서버 덕분에, domain.com 서버에서는 같은 도메인(domain.com)에서 온 요청으로 인식하여 CORS 에러가 발생하지 않습니다.
package.json에 proxy값을 설정
create-react-app 으로 생성한 프로젝트에서는, package.json 에 proxy 값을 설정하여 proxy 기능을 활성화 하는 방법도 있습니다.
{
//...
"proxy": "http://localhost:4000"
}
Reference:
[Web] CORS 동작 방식과 해결 방법 | INGG.
'[항해99]' 카테고리의 다른 글
[항해99] Week 07 회고 / 유튜브 클론코딩 (0) | 2022.02.27 |
---|---|
[항해99] Week 06 회고 / 첫 협업 (0) | 2022.02.20 |
[항해99] Week 04 회고 / ORM, noSQL vs SQL (0) | 2022.02.06 |
[항해99] Week 03 회고 / Restful API, package.json (0) | 2022.01.30 |
[항해99] Week02 회고 / ES? ES5와 ES6 차이 (2) | 2022.01.23 |
댓글