CORS 등장 배경
- 과거 웹사이트를 만들 때, 대부분 하나의 서버에서 브라우저의 모든 요청을 처리
- 점점 웹사이트가 발전하여서 웹사이트에서 할수있는 일이 많아졌음(ex 날씨 api 를 이용하여 기능을 넣을 필요가 있는 경우)
- 웹브라우저와 날씨 api 도메인이 서로 달라서 요청을 주고받을 수 없음 → 서로 도메인이 달라 과거에는 해당 문제를 해결하기 위한 JSONP 방식 사용
- JSONP 방식은 <script> 요소를 사용하여 외부 출처로부터 조회된 내용을 실행하는 것이 허용 → 조회된 내용을 서버에 데이터로 반환하는 용도로 사용
JSONP 방식 예제 → CORS가 나오기 전까지 사용 되던 방식으로 보안상 이슈로 사용 X
# 함수이름을 넣어 요청 const script = document.createElement("script") script.src = "ingg.com/test.json?callback=parseResponse" # 서버에서는 함수이름을 넣고 매개변수로 데이터를 넣어서 반환 parseResponse({ "id" : "123", "name" : "ingg", }) function parseResponse(data){ // ... }
CORS란
- CORS는 다른 출처의 리소스가 필요한 경우, SOP를 우회하기 위한 여러가지 방법 중 가장 권장되는 방법
- 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다. - MDN
SOP 정책
- origin(원본 서버)은 scheme(protocol), host(domain), port로 구성
- 예시) https://www.google.com/maps라는 주소
- protocol → https://
- Host → www.google.com
- Port → :443
SOP 정책이란
- Same Origin(동일 출처 정책)은 scheme, host, port가 모두 같을 때를 의미
- SOP(Same-Origin Policy)란 같은 Origin에서만 리소스를 공유할 수 있다는 규칙
- Same Origin Policy(동일 출처 정책)은 어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식
- Same Origin(동일 출처 정책)은 잠재적으로 해로울 수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄여줌
- 브라우저에서 다른 서버에 요청할 경우 SOP 정책 영향을 받음
- 브라우저를 거치지 않고 서버 간 통신을 할 때는 SOP 정책이 적용 X
SOP 정책이 존재이유
- 다른 출처의 어플리케이션이 서로 통신하는 것에 대해 제약
- 악의를 가진 사용자가 소스 코드를 보고 CSRF(Cross-Site Request Forgery) 나 XSS(Cross-Site Scripting)와 같은 방법을 사용하여 정보를 탈취를 하는 것을 막음
CORS 동작 방식
- CORS 접근 제어 시나리오 (교차 출처 리소스 공유가 동작하는 방식)에는 크게 세가지
- 프리플라이트 요청 (Preflight Request)
- 단순 요청(Simple Request)
- 인증정보 포함 요청(Credentialed Request)
1. 프리플라이트 요청 (Preflight Request)
- Preflight Request는 요청을 예비 요청과 본 요청으로 나눔
- OPTIONS 메서드를 통해 다른 도메인의 리소스에 요청이 가능한지 (실제 요청이 전송하기에 안전한지) 확인 작업을 하고, 요청이 가능하다면 실제 요청을 보냄
- Cross-origin 요청은 유저 데이터에 영향을 줄 수 있기 때문에 Preflight 요청
- Preflight Request
- OPTIONS 요청과 함께 두 개의 다른 요청 헤더가 전송
- Preflight Request가 완료되면 실제 요청을 전송
- 첫 번째 행은 실제 요청을 전송할 때 POST 메서드로 전송된다는 것을 서버에 알려줌
- 두 번째 행은 실제 요청을 전송 할 때 X-PINGOTHER 와 Content-Type 사용자 정의 헤더와 함께 전송된다는 것을 서버에 알려줌
Access-Control-Request-Method: POST # 실제요청의 메서드 Access-Control-Request-Headers: X-PINGOTHER, Content-Type # 실제요청의 추가헤더
- Preflight Response
- 서버가 메서드와 헤더를 받을 수 있음을 알려줌
- 첫 번째 행은 응답할 때 서버측 허가되는 서버 도메인(http://foo.example)를 클라이언트에게 알려줌
- 두 번째 행은 응답할 때 서버측 허가되는 POST, GET, OPTIONS 메서드를 클라이언트에게 알려줌
- 세 번째 행은 응답할 때 서버측 허가되는 X-PINGOTHER 와 Content-Type 사용자 정의 헤더를 클라이언트에게 알려줌
- 마지막 행 은 preflight request에 대한 응답을 캐시할 수 있는 시간(초)를 클라이언트에게 알려줌 → 86400초 (=24시간)
- Preflight를 보내면 사전, 실제 요청 두번이 매번 왔다갔다 하므로 브라우저가 캐싱을 해두고 똑같은 요청을 보낼 때에는, 사전 요청을 보내지 않고 바로 본 요청을 보냄
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 응답 캐시기간
2. 단순 요청(Simple Request)
- Simple Request는 Preflight Request와 다르게 요청을 보내면서 즉시 cross origin인지 확인
- Simple Request는 아래 조건을 모두 충족해야함
- 메서드는 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 등..)을 전송
- credentials 설정을 include/true로 설정하면 CORS정책에 의해 Access-Control-Allow-Origin을 모든 출처를 허용하는 * 로 지정할 수 없다는 에러가 발생
- CORS 설정에서 *을 입력하여 모든 출처를 허용한 경우에는 특정 출처를 정확히 명시
- 인증 정보 포함하여 요청하는 예시
fetch('주소', { credentials: 'include', // 모든 요청에 인증 정보 포함 });
axios 로 통신할 시, withCredentials 설정을 true 로 넣어주면 됨
axios.post(주소, 데이터, { withCredentials: true }); // 또는 공통으로 추가 axios.defaults.withCredentials = true;
CORS 해결 방법
- CORS 정책 위반으로 에러가 발생했을때 해결하는 방법
1. Access-Control-Allow-Origin 응답 헤더 세팅
- 서버측 응답에서 접근 권한을 주는 헤더를 추가하여 해결
app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); // 모든 도메인 res.header("Access-Control-Allow-Origin", "https://example.com"); // 특정 도메인 });
2. CORS 모듈 사용
아무 옵션없이 설정하면 모든 cross-origin 요청에 대해 응답 → 특정 도메인이나 특정 요청에만 응답하게 옵션을 설정하는 것이 좋음
const cors = require("cors"); const app = express(); app.use(cors());
특정 도메인 접근 혀용
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" }); });
3. webpack-dev-server proxy 기능
리액트 개발환경에서, 서버쪽 코드를 수정하지 않고 해결 가능
아래와 같이 프록시 속성을 설정하면, 서버에서 해당 요청을 받아줌
중간의 프록시 서버 덕분에, domain.com 서버에서는 같은 도메인(domain.com)에서 온 요청으로 인식하여 CORS 에러가 발생 X
// 프록시 쓰지 않았을때 // localhost:8080(클라이언트 측) --X (CORS)--> domain.com (서버 측) // 프록시를 설정 후 // localhost:8080(클라이언트 측) --O 프록시가 설정된 Webpack Dev Server--> domain.com (서버 측) module.exports = { devServer: { proxy: { "/api": { target: "domain.com", changeOrigin: true, }, }, }, };
4. package.json에 proxy 값을 설정
- create-react-app으로 생성한 프로젝트에서는, package.json에 proxy 값을 설정하여 proxy 기능을 활성화하는 방법
{ //... "proxy": "http://localhost:4000" }
'HTTP > CORS' 카테고리의 다른 글
XHR (XML Http Request)이란 (0) | 2022.07.24 |
---|---|
4장. AWS S3 + CloudFront로 CDN 구축 시 CORS 강제 설정 (0) | 2022.07.24 |
3장. CORS의 HTTP 응답 헤더와 HTTP 요청 헤더 (0) | 2022.07.24 |
2장. CDN을 통한 CORS 및 CORS 요청 (0) | 2022.07.24 |