출처: https://inpa.tistory.com/entry/WEB-📚-CORS-💯-정리-해결-방법-👏 [Inpa Dev 👨💻:티스토리]
URL을 통해 사이트 접속을 할때, URL의 구성 요소는 아래와 같다.
1. Protocol(Scheme)
: http, https
2. host
: 사이트 도메인
3. Port
: 포트 번호
4. Path
: 사이트 내부 경로
5. Query string
: 요청의 key와 value값
6. Fragment
: 해시 태그
이렇게 URL이 구성 되어있는데 이 중에 Protocol
, host
, Port
를 합친 URL을 출처라고 한다.
Same - Origin Policy(동일 출처 정책)
이 SOP 정책은 동일한 출처에서만 리소스를 공유할 수 있다.
동일 출처(Same-Origin) 서버의 리소스는 자유로이 접근 및 제공이 가능하지만, 다른 출처(Cross-Origin) 서버에 있는 이미지나 유튜브 영상 같은 리소스는 상호작용 불가능.
출처가 다른 두 어플리케이션이 자유로이 소통할 수 있는 환경은 꽤 위험한 환경이다.
출처 제약이 없다면, 해커가 CSRF(Cross-Site Request Forgery)나 XSS(Cross-Site Scripting)등의 방법을 이용해 해커의 코드를 어플리케이션이 실행할 수 있다.
사용자가 악성 사이트에 접속
해커가 몰래 심은 악의적인 자바스크립트가 실행되어, 사용자 모르는 사이에 특정 포털사이트에 요청을 보낸다.
포털 사이트에서 해당 브라우저 쿠키를 이용해 로그인하거나, 개인정보 응답 값을 받은 뒤 사이트에서 해커 서버로 메시지를 보낸다.
이외에도 사용자가 접속중인 내부망의 아이피, 포트를 가져오거나, 사용자 브라우저를 프록시처럼 악용 가능하다.
출처가 같은지 다른지 구분하는 방법은 바로 위에서 설명한 URL을 살펴보는 것이다.
Protocol
, host
, Port
를 합친 URL이 동일하다면 동일 출처로 판단한다.
이 출처 구분은 브라우저에서 한다.
서버는 출처가 달라도 리소스 응답을 해주지만, 브라우저가 동일 출처인지 검사해 에러를 발생한다.
하지만 인터넷은 다른 출처들의 리소스들을 가져와 사용하는 일이 흔하기 때문에,
무작정 막을 수는 없다.
따라서 다른 출처에서 리소스 공유를 할때 조항인 CORS가 필요하다.
Cross-Origin Resource Sharing (교차 출처 리소스 공유 정책)
클라이언트에서 HTTP 요청의 헤더에 출처(Origin)을 담아 전송
서버는 응답헤더에 Access-Control-Allow-Origin을 담아 클라이언트로 전달한다.
클라이언트에서 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교한다.
브라우저는 요청을 보낼 떄 한 번에 바로 보내지 않고, 예비 요청을 보내
서버와 잘 통신되는 지 확인 후, 본 요청을 보낸다.
1. 자바스크립트의 fetch() 메서드를 통해 리소스 받아옴
2. 브라우저는 서버로 HTTP OPTIONS 메서드로 예비 요청을 먼저 보낸다.
3. 서버는 이 예비 요청에 대한 응답으로 어떤 것을 허용하고 금지하는지에 대한 헤더 정보를 담아 브라우저로 보낸다.
4. 브라우저는 보낸 요청과 서버가 응답해준 정책을 비교해, 해당 요청이 안전한지 확인하고 본 요청을 보내게 된다.
5. 서버가 본 요청에 대한 응답을 하면 최종적으로 이 응답 데이터를 자바스크립트로 넘겨준다.
매 요청마다 예비 요청을 보내면 비용낭비가 심하다.
-> 캐싱을 통해 해결
브라우저 캐시를 이용해, Access-Control-Max-Age 헤더에 캐싱 시간 명시해주면,
예비 요청을 캐싱해 최적화 시킬 수 있다.
브라우저는 예비 요청을 할 때마다 Access-Control-Max-Age 값을 확인하여 캐싱된 허용 여부를 검사한다.
해당 요청의 허용 여부가 캐싱되어 있지 않다면, 서버에 예비 요청을 보내 CORS 정책에 따라 허용 여부를 확인한다.
서버가 Access-Control-Max-Age 응답 헤더를 반환하면, 브라우저는 해당 기간 동안 동일한 요청에 대해 예비 요청을 생략할 수 있도록 설정한다.
이후 동일한 요청을 보낼 때, 캐싱된 허용 여부를 기반으로 본 요청을 직접 전송하며 예비 요청을 생략한다.
브라우저에서 예비 요청을 생략하고 바로 서버에 본 요청을 보낸 후, 서버가 이에 대한 응답 헤더에
Access-Control-Allow-Origin 헤더를 보내주면 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다.
요청의 메소드는 GET, HEAD, POST 중에 하나여아한다.
Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width 헤더일 경우 에만 적용된다.
Content-Type 헤더가 application/x-www-form-urlencoded, multipart/form-data, text/plain중 하나여야한다. 아닐 경우 예비 요청으로 동작된다.
까다롭고 대부분의 HTTP API 요청은 text/XML
이나 application/json
으로 통신하기 때문에
위 3번째 조건인 Content-Type을 위반해 사용할 수 없다.
따라서 대부분의 요청은 예비 요청으로 이루어진다.
인증된 요청은 클라이언트에서 서버에게 자격 인증 정보(Credential)를 실어 요청할 때 사용한다.
클라이언트에서 쿠키같은 인증 정보를 포함해 다른 출처 서버로 전달할 때,
인증된 요청으로 동작하며 단순요청이나 예비 요청과는 다르게 동작한다.
클라이언트에서 인증 정보 보내도록 설정
기본적으로 브라우저가 제공하는 요청 API 들은 별도의 옵션 없이 브라우저의 쿠키와 같은 인증과 관련된 데이터를 함부로 요청 데이터에 담지 않도록 되어있다.
이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 credentials 옵션이다. 이 옵션에는 3가지의 값을 사용할 수 있으며, 각 값들이 가지는 의미는 아래와 같다.
옵션 값 | 설명 |
---|---|
same-origin (Default) | 같은 출처 간 요청에만 인증 정보 담을 수 있음 |
include | 모든 요청에 인증 정보 담을 수 있다. |
omit | 모든 요청에 인증 정보 담을 수 없다. |
이렇게 별도 설정을 하지 않는다면, 쿠키등 인증 정보는 자동으로 서버에 전송하지 않는다.
서버에 인증된 요청을 하는 방법으로, fetch 메서드를 사용하거나
다양한 라이브러리에서 제공하는 방법들을 사용하면된다.
서버에서 인증된 요청에 대한 헤더 설정하기
서버도 마찬가지로 이러한 인증된 요청에 대해 일반적인 CORS 요청과는 다르게 대응해야 함.
1. 응답 헤더의 Access-Control-Allow-Credential 항목을 true로 설정해야한다.
2. 응답 헤더의 Access-Control-Allow-Origin값에 와일드카드 문자는 사용 불가능하다.
2. 응답 헤더의 Access-Control-Allow-Methods값에 와일드카드 문자는 사용 불가능하다.
4. 응답 헤더의 Access-Control-Allow-Headers값에 와일드카드 문자는 사용 불가능하다.
인증된 요청 역시 예비 요청처럼 preflight가 먼저 일어난다.
위 그림에서는 생략된 것