웹페이지를 만들 때 api를 사용하다보면 간혹 CORS문제란 것을 만난다. CORS문제가 무엇이며 이에 대한 원인이 무엇인지 보자.
SOP
브라우저에는 SOP, 즉 Same Origin Principle이 있다. 이 정책은 브라우저가 요청받을 수 있는 리소스는 오직 사용자가 요청한 사이트만 접속 가능하다는 원칙이다. 이 원칙은 XSS등 스크립트를 삽입하는 공격을 방어하기 위해 만든 정책이다.
이러한 SOP정책의 문제점은 개발할 때 내가 원하는 외부 리소스를 못사용한다는 것이 있다. 대표적으로 api요청같은 행위를 수행하지 못하도록 막혀진다.
SOP 예외정책
모든 외부 리소스가 SOP에 적용되지 않는다. 다음의 경우에는 적용받지 않는다.
- CORS에 작성된 규칙
- 단순 이미지 리소스
- 스타일 시트
CORS
Cross Origin ReSource의 약어로서 브라우저가 외부 리소스 접근을 허용하는 규칙
을 의미한다. 해당 규칙은 http 응답 헤더에 포함된다. 여러개의 규칙이 있지만 잘 사용되는 것은 다음과 같다.
- Origin: 허용하는 리소스 출처
- Method: 허용하는 메소드들
- Header: 사용가능한 헤더
CORS는 브라우저가 최초로 접속하는 서버, 즉 백엔드가 내려주는 Origin으로 결정이 된다.
물론 *(와일드 카드) 를 통해 방화벽의 any와 같은 동일한 역할을 수행하도록 할 수 있다.
Origin
리소스 출처라는 의미이며 브라우저에서 인식하는 Origin은 url중 다음과 같은 요소를 가져야 한다.
- protocol
- host
- port
여기서 port는 브라우저에 따라 보는 브라우저도 있고 안보는 브라우저가 있다. 그러나 대부분의 브라우저는 port까지 Origin 으로 본다.
Origin에 대해선 간단히 예시로 설명하겠다. 다음의 예시를 보자.
- http://blog.greenflamingo.dev
- https://blog.greenflamingo.dev
이 둘은 서로 다른 Origin이다. 프로토콜이 다르기 때문이다.
- https://blog.greenflamingo.dev
- https://blog.greenflamingo.dev/test
위의 2개의 url을 같은 Origin이다. path는 보지 않기 때문이다.
CORS가 존재하는 서버의 동작과정
사용자, 브라우저, 서버간의 cors의 동작에 대해 설명하겠다. 그림으로 설명하면 다음과 같다.
여기서 보면 cors를 거부하는 것은 서버가 아닌 클라이언트다. 즉 서버는 요청에 대한 처리를 하고난 결과를 반환하지만 클라이언트를 거부하는 형식으로 동작한다.
이를 통해 CORS는 클라이언트의 요청을 막는 것이 아닌 서버에 대한 응답을 막는 것을 알 수 있다. 그래서 delete, put같은 데이터 수정, 삭제같은 무결성을 깨트릴 수 있는 요청을 보낼 때 문제가 발생할 수 있다. 데이터가 수정된 것을 사용자는 모르지만 서버가 알 수도 있는 상황이 발생할 수 있다.
따라서 서버는 CORS만 믿고 모든 요청이 정상요청일 것이라고 생각하고 작성해선 안된다.
CORS활용
여러 api요청 및 응답 요청들을 보고 어떤 요청들에 대해 어떤 CORS들이 있는지 확인해보자.
Preflight Request
예비요청, 본요청으로 나누어서 실제 요청을 수행하기 전에 예비요청으로 응답을 받을 수 있는지를 확인 후 전송한다.
과정은 다음과 같다.
- options메소드로 리소스를 사용할려는 서버에 요청하여 CORS정책을 받아온다.
- 클라이언트는 CORS로 허용 여부를 확인하고 본 요청을 보낸다.
예비 요청을 할 때 다음과 같은 값을 포함한다.
- Origin: 예비요청을 보내는 origin
- Access-Control-Request-Method: 본 요청에서 보낼 메소드
- Access-Control-Request-Headers: 본 요청에서 보낼 헤더들
예비요청에 대한 응답은 다음과 같다.
- Access-Control-Allow-Origin: 서버가 허가한 출처
- Access-Control-Allow-Methods: 허용된 메소드들
- Access-Control-Allow-Headers: 서버가 허가한 헤더들
- Access-Control-Max-Age: preflight 응답 캐시 기간
preflight request의 장점은 미리 cors룰을 확인함에 따라 SOP에 의해 걸리는 요청을 수행하지 않도록 브라우저 측면에서 방어할 수 있다. 다만 1번의 프로세스를 처리하기 위해 2번의 http요청을 해야하는 문제가 있다.
cache를 이용한 preflight request의 딜레이 해결
preflight request에서는 캐시기능을 통해 문제점을 해결하고자 한다. 위에서 예비요청의 응답값에 Access-Control-Max-Age라는 응답 캐시 기간이란 값이 존재한다. 이 값의 의미는 풀어서 설명하자면 예비요청으로 받아온 cors를 캐시로 저장되는 기간을 의미한다. 즉 모든 요청에 예비요청을 보내는 것이 아닌 이미 요청을 보내 cors를 안 서버에 대해선 예비요청을 보내지 않고 내부에서 cors규칙만 확인하고 바로 본 요청으로 보낸다.
why? options메소드의 응답값이 2xx인가
여기서 1번과정에서 options메소드를 정상적으로 호출하였을 경우 2XX번대 응답을 준다. 여기서 중요한 것은 200번이 아닌 2XX번대라는 것이다.
예비 요청에 의해 응답되는 값은 ok status이어야 한다고 javascript의 fetch문서에 적혀있다.여기서 말하는 ok status란 200번이 아닌 2xx번대의 모든 상태코드를 의미한다. http상태코드중에서 2xx번대는 성공에 대한 의미를 가진다. preflight request는 cors를 받는 것이 목적이다. 즉 성공에 대한 의미를 전달하므로 2xx번대면 어떤 상태코드든 응답으로 제공해줘도 문제가 없다고 해석할 수 있다.
물론 개발자들도 200번대에서 아무거나 뽑아서 사용하진 않는다. 대부분 200이나 204상태코드를 사용한다.
200은 단순 성공의 의미로, 204는 성공하였으며 이에 대한 응답값은 없다는 의미로 사용된다.
gin-gonic에서 사용하는 gin-cors에선 성공의 의미로 204상태코드를 제공한다.
Simple Request
단순 요청 및 응답으로서 서버가 항상 정상작동할 것이라고 신뢰하고 본요청을 즉시 보내 응답을 받는다.
Simple Request는 다음과 같은 헤더를 포함해야 한다.
- Content-Type: 요청하는 데이터의 형식
- Accept: 브라우저가 허용하는 데이터 타입. MIME로 표현
- Content-Type: body에 담긴 메세지타입. MIME로 표현
- Accept-Language: 사용자가 이해가능한 자연어
우리가 cors를 몰랐을 때 생각하는 그 http요청이랑 같다.
이 요청의 장점은 간편하다는 것이다. 굳이 2번의 요청을 보내지 않고 바로 본요청을 보내기 때문이다. 단점은 cors를 확인하지 않고 보내기 때문에 cors동작과정에 의해 서버에선 처리되었지만 브라우저에선 처리된 결과를 받을 수 없는 경우가 발생할 수 있다.
Request with Credentials
쿠키와 인증정보같은 서버가 클라이언트를 식별할 수 있는 정보와 같이 요청을 수행한다.
이 요청은 사용하는 이유는 사용자 식별이다. 원래 요청하는 Origin과 통신할 때는 쿠키같은 정보를 요청시 자동으로 생성하고 포함하여 보낼 수 있다. 그러나 CORS룰에 허용된 Origin은 요청값에 쿠키같은 인증관련 정보를 같이 넣어서 보낼 수 없도록 되어있다. 그래서 쿠키같은 인증정보를 포함해서 전송하여 외부 Origin도 클라이언트를 식별할 수 있도록 하기 위함이다.
이 요청을 이용하기 위해선 서버측에서 할 일은 Access-Control-Allow-Credentials를 다음과 같이 설정해야 한다.
Access-Control-Allow-Credentials: true
이 요청을 사용할 경우 옵션에 제약사항이 존재한다. 바로 Access-Control-Allow-Origin에 *(와일드카드)
를 사용할 수 없다는 것이다.
Reference
'CS' 카테고리의 다른 글
[자료구조] 해시맵 (0) | 2022.07.13 |
---|---|
[자료구조]BST, AVL트리 (0) | 2022.07.06 |
[자료구조] 트리 개론 (0) | 2022.07.04 |
[GoF] 2. Factory (0) | 2022.03.22 |
[GoF] Singleton (0) | 2022.03.21 |