CORS 에러 정리 - boostcampwm-2022/web33-Mildo GitHub Wiki
-
URL의 간단한 구조는 아래 그림과 같다.
- HTTP 프로토콜에서 80번 포트는 생략 가능하다.
- HTTPS 프로토콜에서 443번 포트는 생략 가능하다.
-
출처(Origin)란 URL의 구조에서 프로토콜, 호스트, 포트를 합친 것을 말한다.
-
같은 출처의 예시는 아래의 표와 같다.
-
- 같은 출처(Same-Origin)란 프로토콜, 호스트, 포트가 같은 서버를 의미하며, 동일 출처 정책은 다른 출처(Origin)에서 가져온 리소스와 상호작용 하는 것을 제한하는 보안 방식이다.
- XSS나 XSRF 등의 보안 취약점을 노린 공격을 방어할 수 있는 장점이 있다.
- 동일-출처 정책에서 다른 출처의 리소스를 사용하기 위한 예외 조항이 CORS이다.
- 웹 페이지 상의 제한된 리소스를 최초 자원이 서비스된 도메인 밖의 다른 도메인으로부터 요청할 수 있게 허용하는 구조이다.
- CORS는 크게 두 가지 방법이 존재한다.
- 서버에게 바로 요청을 보내는 방법으로, 브라우저는 서버에 API를 요청하고 서버는
Access-Control-Allow-Origin
헤더를 포함한 응답을 브라우저에 보낸다. - 브라우저는 이 헤더를 확인해서 CORS 동작을 수행할지 판단한다.
- 단순 요청 방법은 아래의 3가지 조건을 만족해야 작동한다.
-
요청 메서드(Request method)
는GET
,HEAD
,POST
만 사용 가능하다. -
요청 헤더(Request header)
는Accept
,Accept-Language
,Content-Language
,Content-Type
,DPR
,Downlink
,Save-Data
,Viewport-Width
,Width
만 가능하다.- 그러나 사용자 인증에 주로 사용되는
Authorization
헤더가 포함되지 않는다.
- 그러나 사용자 인증에 주로 사용되는
-
Content-Type
헤더는application/x-www-form-urlencoded
,multipart/form-data
,text/plain
만 가능하다.- 그러나 많은 REST API가
Content-Type
헤더에application/json
을 많이 사용한다.
- 그러나 많은 REST API가
-
- 서버에 예비 요청을 보내서 안전한 서버인지 판단한 후 실제 요청을 보내는 방법이다.
- 예비 요청은 OPTIONS라는 메서드를 사용한다.
- 서버는 브라우저의 예비 요청에 대해
Access-Control-Allow-Origin
헤더를 포함한 응답을 보내고, 브라우저는 이 헤더를 확인해서 CORS 동작을 수행할지 판단한다.
- 요청 헤더
-
Origin
: 요청하는 대상의 출처를 나타내며, API를 호출하는 페이지의 출처 값이 저장된다. -
Access-Control-Request-Method
: 실제 요청이 어떤 HTTP 메서드를 사용하는지 서버에 알려준다. -
Access-Control-Request-Headers
: 브라우저에서 보내는 요청의 커스텀 헤더 이름을 서버에 알려준다.
-
- 응답 헤더
-
Access-Control-Allow-Origin
: 이 헤더에 작성된 출처의 리소스 접근을 허용한다. -
Access-Control-Allow-Credentials
: 요청의credentials
가include
일 때 응답할지 나타낸다. -
Access-Control-Expose-Headers
: 브라우저의 자바스크립트에서 응답의 특정 헤더에 직접 접근할 수 있게 허용한다. -
Access-Control-Max-Age
: 예비 요청 결과를 캐시 할 수 있는 시간을 나타낸다. -
Access-Control-Allow-Methods
: 요청 헤더에 포함된Access-Control-Request-Method
헤더에 대한 응답 결과로, 리소스 접근을 허용하는 HTTP 메서드를 지정한다. -
Access-Control-Allow-Headers
요청 헤더에 포함된Access-Control-Request-Headers
헤더에 대한 응답 결과이다.
-
-
cors 미들웨어 사용
- CORS 응답 헤더를 알아서 추가해주기 때문에, 개발자가 별도로 COSR 응답 헤더를 추가해주지 않아도 된다.
-
JSONP(JSON with Padding)
-
<script>
태그가 외부 출처 리소스를 가져올 수 있는 특징을 사용하는 방법이다.<!-- Frontend --> <!DOCTYPE html> <html> <script> function jsonpFn (data) { console.log(data) // beomy } </script> <script type="application/javascript" src="<http://localhost:3001/cors?callback=jsonpFn>" > </script> </html>
// Backend router.get('/cors', (req, res, next) => { res.send(`${req.query.callback}('beomy')`) })
-
-
프록시 서버(Proxy server)
- 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해 주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다.
- 서버와 클라이언트 사이에 중계기로서 대리로 통신을 수행하는 것을 프록시, 그 중계 기능을 하는 것을 프록시 서버라고 부른다.
- 기능 구현을 위해 브라우저에서 사용자의 거주 지역을 확인해야 하는데, 브라우저가 직접 확인할 수 있는 사용자 위치 정보는 위·경도 뿐이다.
- 네이버 maps API의 Reverse Geocoding을 사용하면 위·경도 정보로 구체적인 주소 정보를 확인할 수 있다.
- 그러나 네이버 maps API는 보안 문제로 인해 브라우저에서 직접 요청할 수 없고, server를 통해서 우회적으로 요청을 해야한다.
- 공식 사용법에 브라우저에서 요청 시 의도적으로 cors 에러를 띄운다고 설명이 되어 있으며, API secret key를 브라우저에서 관리하면 노출되기 때문으로 보인다.
-
우리는 CORS 에러를 해결하기 위해 다음과 같은 코드를 작성하였는데, 매번 이 에러가 발생할 때마다 해결 방법을 찾는 데만 급급해서 이것저것 방법을 다 가져다가 붙여 놓은 모습이다.
-
가장 먼저 브라우저의 요청 헤더에 이것저것 넣어봤는데, 이건 코드가 현재는 없다.
-
서버의 응답 헤더에
Access-Control-Allow-origin
을 설정하였다.// server\\src\\apis\\controllers\\naver.controller.ts try { const data = await getAxiosFromNaverApi(url); if (process.env.CLIENT_URL) { res.setHeader('Access-Control-Allow-origin', process.env.CLIENT_URL); } res.status(200).send(data); } catch (error) { console.log(error); res.status(500).send({ ok: false, message: '좌표값을 기반으로 주소를 가져오지 못했습니다! :(' }); }
-
axios 요청 헤더에
credentials
를 사용하였다.// client\\src\\apis\\axios.ts axios.defaults.withCredentials = true; // 나중에 .env로 빼내어야 함 const serverURL = '<http://localhost:3001>';
const fetchGeocodeFromCoords = async (lat: number, lng: number) => { let data;
try { if (lat && lng) { data = await axios.get(
${serverURL}/api/naver?lng=${lng}&lat=${lat}
); } } catch (error) { console.log(error); }return data?.data.results[0].region.area1.name; };
-
다 안되다가 cors 미들웨어에 이런 저런 옵션을 다 설정해주니 잘 되었다.
// server\\src\\app.ts app.use(cors({ origin: process.env.CLIENT_URL, credentials: true }));
-
-
그러나 최초 에러 메시지는
No ‘Access-Control-Allow-Origin’ header is present…
였는데 너무 많이 잡다한 코드를 넣었다.- 지금도 확인해보니 서버의 응답 헤더에
Access-Control-Allow-origin
만 넣어주면 되는데 COSR 에러를 제대로 이해하지 못하니 이것 저것 건드리니까 더 헤맨 것으로 보인다.
- 지금도 확인해보니 서버의 응답 헤더에
-
axios에서 withCredentials를 사용하면 서버 응답 헤더에
Access-Control-Allow-Headers
를 넣어주어야 하는데 개념 자체를 잘 모르니까 아래 에러도 떴었다.- cors 미들웨어의 옵션으로 credential을 추가하니까 해결됐는데 이게 위 방법이랑 똑같은 것이다.
- 개별 서버 응답 헤더에
Access-Control-Allow-origin
만 설정해도 잘 돌아간다. - 아니면 cors 미들웨어를 사용해서 전체 응답 헤더에 위 헤더를 넣어주면 된다.
- credential은 무조건 지우고 위 두 방법 중 하나만 살려두자!
- 해결 방법만 급급하게 찾으려고 하지 말고 개념을 좀 잘 이해하자~