MDN Web Docs | 교차 출처 리소스 공유 (CORS)
이 글은 CORS에 대해 공부하고 정리한 글입니다.
오타나 부족한 곳이 있다면 마음껏 지적해주세요
CORS
우리가 개발을 진행하다보면 다음과 같은 에러를 많이 만날 수 있습니다.
대충 번역을 해보자면
네가 보낸 요청은 CORS 정책에 의해서 막혔다.
현재 요청에 Access-Control-Allow-Origin 헤더가 없다.
정도로 해석할 수 있습니다.
그럼 우선 CORS는 무엇일까요?
CORS는 Cross-Origin-Resource-Sharing
, 즉 직역을 해보자면 "교차 출처 리소스 공유"로 번역할 수 있겠네요.
다른 출처의 자원을 공유하는 것에 대한 정책이다.
조금 해석을 해보자면 위와 같습니다.
그러니까 우리의 에러는 해당 정책을 어겨서 나는 에러입니다.
그럼 요기서 출처는 무엇일까요?
Origin(출처)?
우리가 Origin
(출처)에 대해서 알려면URL
의 구조를 파헤쳐야 합니다.
URL의 구조
자, 요기 우리가 흔히 보는 URL이 있습니다.
https://www.google.com(:443)/search?title=food#top
(포트번호는 보통 생략됩니다.)
이 URL의 구조를 파헤쳐 보겠습니다.
URL을 파헤쳐 보면 위와 같은 구조로 이루어져 있습니다.
각 부분들은 꼭 존재해야 하는 부분이 있고, 아닌 부분이 있습니다.
(URL에 대해서 요기서 자세히 살펴보진 않겠습니다.)
요기서 Scheme
, Host
, Port
를 합쳐서 우리는 Origin
이라 부르기로 했습니다.
그래서 Scheme
, Host
, Port
세 부분중에 하나라도 다른 곳으로 요청을 보내게 된다면
그것이 Cross-origin
, "교차 출처에 요청을 보냈다"가 됩니다.
Same-Origin Policy
CORS와 반대되는 용어로 SOP(Same-Origin Policy)가 있습니다.
말 그대로 같은 Origin의 리소스에 접근을 하는 것이죠.
아, CORS는 다른 출처에서 요청을 보내서 나타나는 문제이니까
같은 출처에서만 보내면 되지 않을까요?
위와 같은 생각을 할 수 있지만 로컬에서 개발을 할 때도 리액트는 3000번 포트, 노드는 5000번 포트 이렇게 두고 개발을 하는 경우가 많은데, Origin(Scheme
+ Host
+ Port
)에서 Port
번호가 벌써 다릅니다. (이 경우에는 금방 해결할 수 있긴합니다.)
또한 외부 서버에 API 요청을 보내는 일은 꽤, 많이 일어납니다.
그래서 우리 개발자들은 CORS를 피할 수 없는 운명입니다. (맞서 싸웁시다!)
왜 막는거죠?
그럼 왜 이런 정책을 둔 거죠?
그냥 어디에서 요청하는지 신경쓰지말고 알맞은 응답만 보내주면 되지 않을까요?
보안상의 이유로요.
CORS가 없다면 우리는 모든 사이트에 아무런 제약없이 요청을 보낼 수 있습니다.
이게 왜 문제가 되냐구요?
우리가 웹 애플리케이션을 만들었다고 칩시다.
요즘 브라우저들은 너무나도 잘 되어 있어서 개발자도구를 열면 우리의 코드가 어떻게 되어 있는지, 어떤 요청을 보내는지 전부 확인할 수 있습니다. 너무나도 친절하죠?
근데 이 친절함이 악의를 가진 사람들에게는 무기로 작용할 수 있습니다.
악의를 가진 사람들이 우리의 소중한 애플리케이션에 CSRF(Cross-Site Request Forgery)나 XSS(Cross-Site Scripting)와 같은 방법들을 사용해서 우리의 애플리케이션에서 요청을 보낸 것 처럼 위장해서 임의의 요청을 보낼 수 있습니다. 그렇게 우리의 정보를 탈취해간다고 생각해보세요. 억울하지 않나요?
(짜잔 그래서 CORS가 등장했습니다.)
요기서 중요한 것은 요청에 대한 응답을 막는 주체는 서버가 아닙니다.
CORS는 브라우저의 구현 스펙이기 때문에, 브라우저를 통하지 않은 통신은 CORS 정책의 범위에서 벗어납니다. (해결책 중에 하나입니다.)
CORS는 어떻게 동작할까요?
요청에는 세 가지 방식이 있습니다.
Simple
RequestPreflight
RequestCredentialed
Request
Simple request
Simple Request
는 말 그대로 단순 요청입니다.
클라이언트에서 요청을 보내면 브라우저가 그 요청을 받고, 다시 서버에 요청을 보냅니다.
서버는 이에 대한 응답 보낼 때 Access-Control-Allow-Origin
헤더에 "이 리소스에 접근하는 것이 허용된 출처"를 명시해서 전송합니다. 그러면 브라우저에서 자신이 보냈던 Origin
과, 서버에서 보내준 응답의 Access-Control-Allow-Origin
헤더를 비교해서 유효한 응답인지 아닌지 판단합니다.
위의 그림에서는 Access-Control-Allow-Origin
헤더에 와일드카드(*
)를 붙여 "이 리소스에 접근하는 것을 모든 출처에게 허용한다."라고 명시하는 것입니다. 또는 Access-Control-Allow-Origin
헤더에 허용할 출처를 명시해주는 방법도 있습니다.
와일드카드를 너무 남발하면 모든 출처에 대해서 내 자원을 허락하기 때문에 누구나 우리의 데이터를 가져갈 수 있어 Access-Control-Allow-Origin
헤더는 명시를 해주는 편이 좋습니다.
그런데 Simple request
는 특정 조건을 만족할 때만 보내게 됩니다.
단순한데 단순하지 않은 그런 느낌인거죠.Simple request
을 보내게 되는 조건은 다음과 같습니다.
- HTTP method가
GET
,POST
,HEAD
중에 하나 이거나, 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
중에 하나 일 경우에만Simple request
로 인식합니다.
우리가 개발을 하다보면 위의 조건을 지키기가 어렵습니다.
1번은 괜찮지만, 2번은 Authorization
헤더(인증 관련 헤더)도 없고,
3번에서 Content-Type으로 application/json
도 없기 때문에 지켜지기 어려운 조건입니다.
Preflight
Simple request
가 아니면서 cross-origin
요청은 대부분 Preflight request
를 보내게 됩니다.
Preflight
요청은 HTTP OPTIONS
메서드를 통해 실제 요청을 보낼지 말지 판단하는 요청입니다. 본 요청을 보내기 전에 예비 요청을 보내는 것이죠.
예비 요청을 먼저 보내면 서버는 그 응답으로 Access-Control-Allow-Origin
헤더를 포함한 응답을 브라우저에 보냅니다. 그러면 브라우저는 Access-Control-Allow-Origin
헤더를 확인해서 본 요청을 보낼지 말지 결정합니다.
Credentialed Request
마지막 Credentialed Request
은 조금 특수한 경우입니다.
보통은 요청을 보낼 때 별도의 옵션 없이는 쿠키 혹은 인증과 관련된 헤더를 요청에 담지 않습니다. 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 credentials
옵션입니다.
credentials
옵션에는 총 세 가지가 있습니다.
same-origin
: (기본값) 같은 출처 간 요청에만 인증 정보를 담을 수 있다include
: 모든 요청에 인증 정보를 담을 수 있다omit
: 모든 요청에 인증 정보를 담지 않는다
만약 credentials
옵션에 include
를 준다면 브라우저는 조금 더 깐깐하게 조건 검사를 하기 시작합니다.
Access-Control-Allow-Origin
에는*
를 사용할 수 없습니다. 반드시 출처를 명시해야 합니다.- 응답 헤더에는
Access-Control-Allow-Credentials: true
가 존재해야 합니다.
위의 두 조건이 만족되지 않으면 브라우저는 또 에러를 일으킵니다.
CORS를 해결하는 방법
서버에 Access-Control-Allow-Origin 헤더 세팅하기
CORS 정책 위반으로 인한 문제를 해결하는 가장 대표적인 방법은, Access-Control-Allow-Origin
헤더에 알맞은 값을 세팅해주는 것입니다.
위에서 설명했듯, 와일드카드 *
를 사용해 모든 요청에 대한 허용을 할 수 있지만, 내 데이터를 마음껏 아무나 다 써라 하는 것과 같기 때문에 명시를 해주는 것이 좋습니다.
Proxy 서버 사용
보통 우리가 CORS 때문에 애를 먹는 것은 우리가 서버 코드를 작성할 수 없을 때입니다.
외부의 서버는 우리가 어떻게 할 수가 없는데 이럴 때는 Proxy
를 이용하면 됩니다.
CORS는 브라우저가 일으키는 에러입니다. 그래서 서버 <-> 서버로 요청을 보내게 되면 CORS의 감시망에서 벗어날 수 있습니다.
Proxy 서버를 추가로 만들어서 클라이언트에서 우리가 새로 만든 Proxy 서버에 요청을 보내고
Proxy 서버에서 원하는 서버에 요청을 다시 보내면 브라우저가 개입되지 않았기 때문에 CORS 오류를 회피할 수 있습니다.
마무리
오늘은 CORS
에 대해서 알아봤습니다.
해당 글은 CORS
에 대한 전반적인 내용을 설명하였습니다.
조금 더 자세히 알고싶다면 밑에 참고의 다양한 글을 읽어보는 것을 아주 강추합니다.
참고
CORS란? | Beomy 블로그
CORS란 무엇인가? | Yunseok
CORS란 무엇인가: CORS는 브라우저 하는 일 | valuefactory
교차 출처 리소스 공유 (CORS) | MDN Web Docs
CORS는 왜 이렇게 우리를 힘들게 하는걸까? | Evans Library
CORS는 대체 뭐 하는 놈일까? | Jeong Arm
'개발지식' 카테고리의 다른 글
[개발지식] Webpack과 Babel은 왜 쓰이는 건지 알고 계신가요? (0) | 2022.01.26 |
---|---|
[개발지식] 브라우저 저장소, 각각에 대해서 자세히 알고 계신가요? (0) | 2022.01.21 |
[개발지식] Client Side Rendering & Server Side Rendering 차이점에 대해서 아시나요? (0) | 2022.01.19 |
[개발지식] 브라우저 동작방식에 대해서 알고 계신가요? (0) | 2022.01.13 |
[개발지식] 웹 서버와 웹 애플리케이션 서버의 차이, 정확히 알고 계신가요? (0) | 2022.01.11 |