## CORS란 무엇인가 Cross-Origin Resource Sharing의 줄임말. 브라우저에서 보안상의 이유로 제한하고 있는 다른 출처의 리소스에 대한 접근을 서버가 명시적으로 허용 의사를 밝힌 경우에는 브라우저가 응답을 허용하도록 하는 매커니즘이다. ### CORS 정책은 왜 존재하는가 브라우저에서는 기본적으로 SOP (Same-Origin Policy)를 사용하기 때문에 웹 페이지와 다른 출처의 응답을 읽지 못하게 막는다. 하지만 웹의 발전으로 다른 출처의 리소스를 사용해야 하는 경우가 필요해졌고, 이 요청들을 안전하게 허용하기 위한 매커니즘이 필요해졌다. 이게 바로 CORS ### 출처, Origin이 무엇을 의미하는가 ![[8636541f-ff8b-44b9-8d24-c7b77849d0c5.png]] 여기서 Protocol부터 Port까지가 "출처"이다. 이 출처는 브라우저에서 요청을 보낼 때 Origin 헤더에 함께 담아서 보낸다. ### CORS 정책 위반은 어떤 경우에 발생하는가 서버의 응답이 CORS 정책을 위반한 경우에 브라우저가 판단해서 에러를 발생시킨다. 다른 출처로부터 오는 응답을 받았을 때 - 응답 헤더 중에 Access-Control-Allow-Orign이 존재하지 않는 경우 - 요청의 origin과 Access-Control-Allow-Origin이 다른 경우 - (쿠키를 포함한 요청에서) - Access-Control-Allow-Credentials가 명시되어 있지 않거나 - Access-Control-Allow-Origin이 wildcard(\*)로 되어 있는 경우 에 CORS 정책 위반이 발생한다. ## CORS 동작 매커니즘 ### Preflight 요청을 보내는 경우 단순 요청이 아닌 경우에는 실제 요청을 보내는 것이 안전한지를 판단하기 위해서 브라우저가 먼저 OPTIONS 메서드를 이용해서 다른 출처의 리소스에 HTTP 요청을 보낸다. 1. 브라우저가 보내는 Preflight 요청 헤더 ```http OPTIONS /doc HTTP/1.1 Origin: https://foo.example Access-Control-Request-Method: POST Access-Control-Request-Headers: content-type,x-pingother ``` 2. Preflight 요청에 대한 응답 ```http HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400 ``` 요렇게 Preflight 요청을 보내스 응답을 받으면 브라우저는 Origin, Methods, Headers를 확인해서 실제 요청과 맞는지 확인한다. 만약에 다르다면 CORS 에러를 내뱉고 응답을 차단하는 것. 중요한 것은 서버에서는 정상적으로 응답을 보냈다는 것이다. OPTIONS 요청에 대한 응답은 200이나 204등 성공 응답이 오지만 브라우저에서는 해당 응답을 차단한다. ### 단순 요청 (Simple requests) 단순 요청에서는 Preflight를 생략하고 바로 본 요청을 보낸다. 본 요청에 대한 응답으로 오는 `Access-Control-Allow-*` 헤더들을 확인해서 CORS 정책 위반 여부를 확인하는 것이다. [단순 요청(Simple requests)](https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CORS#%EB%8B%A8%EC%88%9C_%EC%9A%94%EC%B2%ADsimple_requests) 단순 요청이란 아래 조건을 **모두** 충족하는 요청이다. - GET, HEAD, POST 메서드를 사용한다. - 사용자 에이전트가 자동으로 설정한 헤더 외에 아래 헤더를 제외한 다른 헤더가 없어야 한다. - Accept - Content-Type - Accept-Language - Content-Language - Range - Contet-Type으로 지정한 미디어 타입은 아래 중 하나여야 한다. - multipart/form-data - text/plain - application/x-www-form-urlencoded ### Credentialed requests 기본적으로 fetch API나 XMLRequest 객체는 쿠키나 인증 정보를 함께 보내지 않는다. 하지만 인증 정보를 함께 보내야 하는 경우에는 credentials 옵션을 사용해서 함께 보낼 수 있다. ``` fetch('https://yoouyeon.dev", { credentials: "include" }); ``` `credentials` 옵션에 들어갈 수 있는 값들은 아래와 같다. - `same-origin` (기본값) : 같은 출처 간 요청에만 인증 정보를 담는다. - `include` : 모든 요청에 인증 정보를 담는다. - `omit` : 모든 요청에 인증 정보를 담지 않는다. credentials 옵션 값으로 include가 설정된다면 다른 출처로의 요청에도 인증 정보가 함께 날아가게 된다. 이것이 바로 Credential Requests Credential Requests를 받은 서버는 Access-Control-Allow-Credentials 헤더에 true 값을 포함해서 응답해야 한다. 그러지 않은 경우에는 응답이 무시됨 Credential Requests에 대한 응답에는 좀 더 까다로운 조건이 있다. Access-Control-Allow-Credentials: true 헤더를 가진 다른 출처 응답은 - Access-Control-Allow-Orgin 헤더 값으로 와일드카드를 사용하면 안되고 명시적인 출처를 지정해야 한다. - Access-Control-Allow-Methods 헤더 값으로 와일드카드를 사용하면 안되고 명시적인 메서드 이름 목록을 지정해야 한다. - Access-Control-Allow-Headers 헤더 값으로 와일드카드를 사용하면 안되고 명시적인 헤더 이름 목록을 지정해야 한다. 요청에 Access-Control-Allow-Origin 헤더에 와일드카드가 존재하면 브라우저는 응답을 무시하고 CORS 에러를 뱉는다. ### 헤더 정리 - **요청 헤더** - `Access-Control-Request-Method` OPTIONS 메서드와 함께 사용. 실제 요청이 전송될 때 어떤 메서드를 사용할 것인지를 알려줌 - `Access-Control-Request-Headers` OPTIONS 메서드와 함께 사용. 실제 요청이 전송될 때 어떤 헤더와 함께 사용할 것인지 알려줌 - **응답 헤더** - `Access-Control-Allow-Origin` 여기에 명시한 출처에서 보낸 요청만 이 리소스에 접근할 수 있다. - `Access-Control-Allow-Methods` 이 리소스를 요청할 때 여기에 명시한 메서드만 사용할 수 있다. - `Access-Control-Allow-Headers` 요청에 사용할 수 있는 헤더가 무엇인지 알려줌. - `Access-Control-Max-Age` 사전 요청에 대한 응답을 얼마나 캐싱할지 알려줌. 기본 값은 5초이다. ## CORS 위반 해결 방법 ### Access-Control-Allow-Origin 설정하기 완전 정석 방법. Access-Control-Alloow-Orign 헤더에 CORS 정책에 맞는 적절한 값을 넣어주면 CORS 에러는 발생하지 않을 것이다. 이 때 와일드카드로 설정하게 되면 모든 출처에서 해당 서버의 리소스를 받아 쓸 수 있다는 뜻이 되므로, 보안적으로 심각한 이슈가 생길 수 있을 뿐 아니라 인증 정보를 사용해야 하는 경우에는 어차피 와일드타드를 사용할 수 없다. 가급적이면 허용 출처를 나타낼 때에는 와일드카드 대신 명시적으로 출처를 명시해주는 것이 좋다. ### 프록시 서버 만들기 로컬에서 개발할 때 발생하는 CORS를 해결하기 위해서 http://localhost:3000 같은 범용적인 주소를 허용 출처에 넣어주는 것은 좀 드물기도 하고 위험한 일일 수 있다. 이 때 선택할 수 있는 방법이 Webpack Dev Server를 이용하는 방법이다. [CORS는 왜 이렇게 우리를 힘들게 하는걸까?](https://evan-moon.github.io/2020/05/21/about-cors/#webpack-dev-server로-리버스-프록싱하기) ```javascript module.exports = { devServer: { proxy: { '/api': { target: 'https://api.evan.com', changeOrigin: true, pathRewrite: { '^/api': '' }, }, } } } ``` 이렇게 설정해두면 ‘/api’로 시작하는 URL로 보내는 요청에 대해서 브라우저는 `https://localhost:8000/api`로 보낸 것으로 알고 있겠지만 뒤에서 웹팩이 `https://api.evan.com` 으로 프록싱해줘서 마치 CORS 정책을 우회한 것 처럼 속일 수 있다 (!!) ## CORS 우회 방법 (브라우저 확장을 사용하는 방법은 생략...) JSONP는 CORS가 표준이 되기 이전에 SOP 정책을 우회하기 위해 사용하던 방법인데, script 태그로 보내는 요청은 SOP의 영향을 받지 않는다는 것을 이용하는 원리이다. script 태그로 get 요청을 보내는데, 요청의 쿼리 파라미터에 callback을 함께 담아서 보낸다. -> 서버에서는 콜백에 데이터를 담아서 자바스크립트 코드 형태로 (application/javascript or text/javascript) 응답한다. `callback({name: “바보”})` -> 응답을 받은 클라이언트는 응답 body에 있는 스크립트를 그대로 실행한다. JSONP는 검증 없이 서버의 응답을 그대로 사용하기 때문에 악의적으로 잘못된 스크립트를 담아 응답하는 경우에는 문제가 발생할 수 있다는 것과 , GET 요청만 사용할 수 있다는 단점이 있다. ## 참고 - [프로그래머 면접 질문: CORS 문제에 대해 설명하시오 - YouTube](https://www.youtube.com/shorts/WndQfplpDJc) - [교차 출처 리소스 공유 (CORS) - HTTP \| MDN](https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CORS) - [05-01 CORS 정책 \| 개발자 기술면접 꼬리물기 질문](https://chained-tech-interview-questions.gitbook.io/ctiq/05-web/05-01-cors) - [CORS는 왜 이렇게 우리를 힘들게 하는걸까?](https://evan-moon.github.io/2020/05/21/about-cors/) - [\[면접 스터디\] 프론트엔드 브라우저와 네트워크 그리고 렌더링 질문 :: 2023년부터 로그 기록](https://var-log.tistory.com/313) - [CORS errors - HTTP \| MDN](https://developer.mozilla.org/ko/docs/Web/HTTP/Guides/CORS/Errors)