CORS는..... 뭘까?
Cross Origin Resource Sharing이다.
해석하면 교차 출처 리소스 공유...
영 감이 오지 않는다.
단어를 하나씩 뜯어보자.
나머지는 알겠는데 Origin이 뭔지 모르겠다.
Origin
Origin(출처)는 URL에서 스키마, 호스트, 포트를 합한것을 뜻한다.(출처 : https://docs.w3cub.com/http/headers/origin)
Same Origin이라는건?
그렇다 스키마, 호스트, 포트가 다 같다는 것이다.
예를들어 https://mysite.com:80 에서 https://mysite.com:80/mypage 를 요청하는 경우, same origin이다.
Cross Origin은?
Same-Origin이 아닌 경우다.
예를 들면 https://naver.com에서 https://google.com으로 요청을 보내는 경우다.
다만, 포트번호도 Origin의 구성요소라는 것을 고려할 필요가 있다.
그렇기 때문에 스키마와 호스트네임이 같다해도 Cross-Origin이된다.
예를들어 http://localhost:3000에서 http://localhost:8000으로 요청을 보내는 것도 Cross Origin이다.
Cross Origin 요청에서 발생할 수있는 일들
정말 극단적이지만 나름대로 예시를 만들어봤다.
fetch('http://unsafeSite.com')
.then(res=>res.json())
.then(res=>exec(res))
받은 데이터를 바로 exec 시켜버리는 코드가 있다고 했을때
여기서 서버가 악의적인 코드를 응답으로 내려주기만 하면 클라이언트 사용자는 꼼짝없이 당할 수 밖에 없다.
예를들어 쿠키나 토큰을 훔쳐가는 코드인 경우, 피해가 아주 커질 것이다.
사실 누가 이런 코드를 쓰겠냐만은...나름대로 예시를 만들어봤다. 분명 더 복잡하고 악의적인 코드가 있을 것이다. 그렇게 생각해주면될 것 같다.
이런 악의적인 해커로부터 인터넷을 보호하기 위해 SOP가 등장한다.
SOP (Same Origin Policy)
동일 출처 정책이다.
즉, 동일한 출처여야만 자원 공유가 가능하다는 것.
https://developer.mozilla.org/ko/docs/Web/Security/Same-origin_policy
그래서 브라우저는 동일한 출처가 아닌 경우, 이걸 막아버린다.
엥 근데 이미지랑 폰트같은건 어떻게 받아옴? 걔네는 외부 자원 아님?
위의 문서를 읽어보면 알겠지만, 교차 출처 네트워크에 쓰기(POST)는 보통 허용한다.
또한 교차출처 삽입(html태그 형태로 리소스를 받아오는 것)도 허용한다. 그러나 교차 출처 읽기는 보통 허용하지 않는다.
이것만 생각하면, 우린 외부 사이트에서 네이버 지도를 불러올 수도 없다.
어떻게 이걸 허용해주지?
CORS
이걸 해주는게 바로 CORS다.
아, 막는게 CORS가 아니라 열어주는게 CORS였다.
그랬다.
CORS는 어떻게 동작하는가
CORS는 헤더로 판단된다.
동일하지 않은 출처에 대한 요청을 하는경우, 브라우저는 요청에 Origin이라는 헤더를 담는다.
Origin에는 요청을 보낸 사이트의 origin이 담긴다.
서버는 이걸 받은다음에 Origin을 검사한다.
요청을 받아들이기로 했다면 Access-Control-Allow-Origin을 응답에 추가한다.
이게 바로 우리가 간간히 CORS에러가 나면 서버에서 *로 처리해버렸던 그것이다.
(*로 처리해주게되면 쿠키-세션으로 인증인가를 처리하는 경우 문제가 발생할 수 있다.)
아래는 express에서 Access-Control-Allow-Origin을 추가하는 예시다.
const corsOptions = {
origin: 'http://localhost:9000',
};
app.use(cors(corsOptions));
클라이언트는 서버로 부터 응답을 받은 뒤에 Access-Control-Allow-Origin이 있는지를 확인해서 있다면 응답에 접근하고, 없다면 에러가 발생한다.
다시 이 이미지를 보자.
항상 시뻘건 메시지를 보고 놀라 구글에 복붙했더라면... 지금에라도 해석을 해보자.
http://localhost:8000에서 http://localhost:3000으로 보낸 요청에 접근하는게 CORS정책에 의해 막혔습니다. : Access-Control-Allow-Origin 헤더가 요청한 리소스에 없습니다.
CORS자체가 에러가 아니고, "CORS 정책에 안맞으니 header를 추가해" 라는 뜻이다.
오류 메시지가 참 친절했는데 왜 진작에 안읽었을까 싶다.
근데 이걸 등록하려면, 서버 담당자한테, "우리 사이트 주소(오리진) 좀 추가해주쇼"라고 말을 해야한다.
클라이언트와 서버간에 합의가 이뤄졌다는 징표인 것이다.
근데 클라이언트에서 서버로 이상한 요청보내면 어떡해요?
그래서 CORS는 요청을 두가지 방식으로 나눠서 서버를 보호한다.
CORS의 두가지 요청
1. 안전한 요청
2. 안전하지 않은 요청
안전한 요청
안전한 요청의 조건은 아래와 같다.
1. 안전한 메서드 - GET, POST, HEAD
2. 안전한 헤더 - Accept, Accept-Language, Content-Language, 값이 application/x-www-form-urlencoded이나 multipart/form-data, text/plain인 Content-Type
이 두가지를 충족하지 않으면 안전하지 않은 요청으로 취급된다.
가령 PUT메서드를 쓴다던가, 헤더에 api-key가 포함된다던가, authorization이 있다던가...하면 안전하지 않은 요청인 것이다.
안전한 요청은 그냥 바로 본요청이 서버로 가게된다.
안전하지 않은 요청
안전하지 않은 요청은 예비 요청을 먼저 하게된다.
이게 바로 preflight요청이다.
https://developer.mozilla.org/ko/docs/Glossary/Preflight_request
이 요청은 OPTIONS 메서드를 사용하고, 두가지 헤더가 들어가며, 본문은 비게된다.
1. Access-Control-Request-Method 헤더 – 해당 요청(안전하지 않은 요청)에서 사용하는 메서드 정보가 담겨있다.
2. Access-Control-Request-Headers 헤더 – 해당 요청(안전하지 않은 요청) 에서 사용하는 헤더 목록이 담겨있다.
그러면 서버는 이걸보고 파악을 하는 것이다. 그리고 괜찮다면 클라이언트에게 200인 본문이 없는 응답을 헤더와 함께 브라우저에게 보낸다.
1. Access-Control-Allow-Origin – *이나 요청을 보낸 오리진 이어야 한다.
2. Access-Control-Allow-Methods – 허용된 메서드 정보가 담겨있다.
3. Access-Control-Allow-Headers – 허용된 헤더 목록이 담겨있다.
4. Access-Control-Max-Age – 퍼미션 체크 여부를 몇 초간 캐싱해 놓을지를 명시한다. 이렇게 퍼미션 정보를 캐싱해 놓으면 브라우저는 일정 기간 동안 preflight 요청을 생략하고 안전하지 않은 요청을 보낼 수 있다.
이렇게 보면 상당히 어지럽고, 머리에도 안들어온다. 예시를 통해 알아보자.
클라이언트 : 오, 나 POST 메서드 쓸껀데 괜춘? 헤더는 Content-Type거임
OPTIONS /about HTTP/1.1
Access-Control-Request-Method: POST //야 POST로 보낸다
Access-Control-Request-Headers: Content-Type //Content-Type헤더 담을꺼임
Origin: https://www.naver.com //내 주소는 이거임
서버 : 오 괜춘함, 요청해줘
HTTP/1.1 200 OK
Access-Control-Allow-Origin: * //야 전부다 Okay임
Access-Control-Allow-Headers: Content-Type //ㅇㅋ 헤더 이거 보내셈
Access-Control-Allow-Methods: OPTIONS, POST //options이랑 post면 okay
이렇게 preflight 요청과 응답이 제대로 이뤄졌다는 것은, 서버가 보내도 된다고 한 것이므로 본요청을 보낼 수 있게된다.
이로써 서버도 CORS에 의해 보호받게되는것이다.
다음에는 CORS와 쿠키에 대해 조금 더 알아볼 것이다.