최근 회사에서 서비스 홈페이지를 리뉴얼하는 업무를 맡아서 하고 있다.
백엔드 개발자 없이 프론트단만 업데이트를 진행하고 있는데, 기존에 있는 로그인 api 에 post 요청을 보내니 CORS 에레가 발생했다.
이전에 부트캠프에서 프로젝트를 진행했을 때, 겪었던 문제였지만 당시에 백엔드 개발자분이 서버단에서 해당 에러를 해결하기 위한 처리를 진행해주셔서 수월하게 해결했었다.
이번에 백엔드 개발자 없이 해당 문제를 해결하게 되면서 공부했던 내용들을 정리하고 백엔드 개발자가 없을 때 프론트 단에서 어떻게 해결하였는지 기록하고자 한다.
SOP ( Same Origin Policy)
우선 CORS 를 이해하기 위해 SOP 에 대해 알고 있어야 한다.
Same Origin Policy, 즉 동일 출처 정책은 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 보안방식이다.
이를 통해 잠재적인 위험이 있는 요청들을 분리함으로써 공격 받을 수 있는 경로를 줄여주는 역할을 한다.
그렇다면 Origin (출처) 란 무엇일까?
위 사진을 보면 URL 이 어떻게 구성되어있는지를 확인할 수 있다. 이때 서버는 URL 의 Protocol, Host, Port 를 비교하여 출처를 확인한다. 이 3가지가 동일해야 같은 출처로 취급된다. ( 단, IE 의 경우에는 port가 달라도 같은 출처로 판단한다.)
Q. http://localhost 와 동일 출처인 url은 모두 무엇일까요?
1. https://localhost
2. http://localhost:80
3. http:// 127.0.0.1
4. http://localhost/api/cors
퀴즈 정답은 맨 아래에..
SOP를 왜 사용해야 할까?
악의를 가진 사람이 CSRF(Cross-Site Request Forgery) 나 XSS(Cross-Site Scripting) 같은 방법을 사용하여 사용자의 어플리케이션에서 코드가 실행된 것처럼 꾸며서 사용자의 정보를 탈취하기 쉬워진다.
따라서 출처가 다르다면 Cross Origin 이므로 SOP 정책에 위반하기 때문에 해당 요청에 대한 응답은 처리되지 않는다.
CORS ( Cross Origin Resource Sharing)
CORS 정책은 우리가 가져오는 리소스들이 안전한지 확인하는 관문이다.
CORS 즉, 교차 출처 리소스 공유는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.
CORS 가 필요한 경우
이번에 내가 겪은 에러를 예로 들어 설명해자면
백엔드 서버가 https://tistory.com 에 배포되어져 있는 상태였고,
프론트 서버의 URL이 http://localhost:3000 이었다.
프론트 서버와 백엔드 서버가 전혀 다른 출처(Origin)로써 SOP 정책에 어긋나기 때문에 서버에 요청을 보냈을 때 브라우저에서 CORS policy 에러가 발생했다.
응답 자체가 오지 않기 때문에 이러한 경우 개발된 코드가 정상적으로 백엔드 서버와 통신하는지 확인할 수 없다.
따라서 CORS 를 통해 이를 해결해줘야 한다.
CORS 3가지 접근 제어 시나리오
실제 CORS 가 동작하는 방식에는 3가지 시나리오가 있다.
- 예비 요청 (Preflight Request)
- 단순 요청 (Simple Request)
- 인증정보 포함 요청(Credentialed Request)
Preflight Request ( 예비요청 )
브라우저가 요청을 보낼때 실제 요청을 바로 보내지 않고 실제 요청을 보내기에 앞서 예비요청을 먼저 보내는 것을 Preflight 라고 한다.
Preflight는 실제 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 확인하는 것이다.
Preflight가 동작하는 방식
1. OPTIONS 메서드를 통해 서버에 다른 도메인의 리소스에 요청이 가능한 지 예비 요청을 보낸다.
2. 요청이 가능하다면 실제 요청(Actual Request)을 보낸다.
서버에 요청을 보낼때마다 preflight 요청과 실제 요청 2번의 요청이 보내진다. 리소스 낭비를 막기 위해 Access-Control-Max-Age 값으로 Preflight 응답에 대한 캐시 시간을 지정함으로써 prefilght 요청이 캐싱이 되어있다면 바로 본 요청이 보내지도록 한다.
Preflight Response 가 가져야 하는 특징
- 응답 코드는 200대 여야 한다.
- 응답 바디는 비어있는 것이 좋다.
Simple Request ( 단순 요청)
Preflight 요청 없이 바로 요청을 보내면서 그 즉시 Cross-Origin 인지 확인하는 절차
즉, Simple Request는 예비 요청을 생략하는 것이다.
Access-Control-Allow-Origin: * => 와일드 카드 형식 (모든 Origin 에 대해 허가를 해주겠다.)
만약 Access-Control-Allow-Origin: http://localhost:8080 이라면 Cross Origin 이다.
아무때나 예비요청을 생략할 수 는 없고 다음 조건을 모두 만족해야 한다.
1. 요청 메소드는 GET, POST, HEAD 중 하나
2. Content-Type 을 사용하는 경우에는 다음의 값들만 허용한다.
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
3. 유저 에이전트가 자동으로 설정한 헤더 외에 수동으로 설정할 수 있는 헤더는 Fetch 명세에서 CORS-safelisted request-header 로 정의한 헤더만 사용할 수 있다. 주로 다음 4가지가 사용된다.
- Accept
- Accept-Language
- Content-Language
- Content-Type
Preflight 가 필요한 이유?
Preflight
Preflight가 왜 있는가에 대해서는 쉽게 말해서 CORS spec이 생기기 이전에 만들어진 서버들은 브라우저의 SOP(same origin policy) request만 가능하다는 가정하에 만들어졌는데, cross-site request가 CORS로 인해서 가능해졌기 때문에 이런 서버들은 cross-site request에 대한 security mechanism이 없다보니 보안적으로 문제가 생길수 있으니 이런 서버들을 보호하기 위해 CORS spec에 preflight request를 포함한겁니다. Preflight request로 서버가 CORS를 인식하고 핸들할수있는지 먼저 확인을 함으로써 CORS를 인식하지 못하는 서버들을 보호할수있는 매커니즘 입니다.
Credentialed Request ( 인증된 요청 )
기존 예비 요청에서 보안을 더 강화하고 싶을 때 사용하는 것으로 인증 관련 헤더를 포함한다.
브라우저가 기본적으로 제공하는 비동기 리소스 요청 API인 XMLHttopRequest 객체나 fetch API 는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 요청에 담지 않는다.
Credentials 옵션을 사용하면 요청에 인증과 관련된 정보를 자동으로 담아서 보낼 수 있다. 단, 클라이언트와 서버 양측에서 모두 설정이 필요하다.
클라이언트 측 credentials: include |
서버측 Access-Control-Allow-Credentials : true |
(Access-Control-Allow-Origin: * 은 안된다. 출처를 정확하게 지정해줘야 한다.)
CORS 해결하기
1. 프론트 프록시 서버 설정 (개발 환경)
2. 직접 헤더에 설정해주기
3. 스프링 부트를 이용하기
프론트 단에서 간단하게 해결하기
다음의 방법들은 근본적인 해결 책은 아니고 당장 어플리케이션이 제대로 동작하는지 확인하기 위해 임의로 사용하는 방식으로 추후 개발이 완료가 되고 배포를 목적으로 한다면 위에 해결하기 방법을 찾아서 근본적으로 문제를 해결하는 것이 좋다.
나의 경우에도 추후 개발이 완료되면 같은 서버에서 배포될 예정이므로 당장 개발단계에서 서버통신을 확인하기위해 2가지 방법을 사용하여 CORS 에러를 해결하였다.
1. Chorme 확장 프로그램 이용
크롬 Web store 에서 "Allow CORS: Access-Control-Allow-Origin" 익스텐션을 설치한다.
해당 프로그램을 활성화 시키게 되면, 로컬환경에서 API를 사용시, CORS 문제를 해결할 수 있다.
하지만 나의 경우 서버단은 배포가 된 상태였고, 프론트 서버만 로컬환경에 있었기 때문에 이것으로 CORS 에러를 해결할 수 없었다.
2. CORS 없이 크롬 열기
open -na Google\ Chrome --args --user-data-dir=/tmp/temporary-chrome-profile-dir --disable-web-security --disable-site-isolation-trials
터미널에서 위 코드를 입력하면 새로운 크롬창이 하나 열리는데, 해당 크롬 창에서는 CORS 에러 없이 개발을 진행할 수 있다.
참고자료
유튜브 - [10분 테코톡] 🌳 나봄의 CORS
[WEB] 📚 CORS 개념 💯 완벽 정리 & 해결 방법 👏
퀴즈정답
2, 4
1번 프로토콜이 다르다.
2번 포트 번호가 있지만 기본 포트인 80 포트 이므로 동일 출처이다.
3번 127.0.0.1 은 로컬 호스트의 ip 주소가 맞기는 하지만 브라우저에서는 url의 string value로 비교하기 때문에 동일 출처로 인식하지 않는다.
4번 protocol과 host가 동일하고 포트 번호가 지정되어있지 않기 때문에 기본 포트번호인 80포트가 할당될 것이므로 동일 출처이다.
'Web' 카테고리의 다른 글
NextJS 프로젝트 배포하기 ( with. Github Acion, 가비아 ) (0) | 2023.01.27 |
---|---|
WEB | 프론트엔드 개발자로서 알아야할 HTTP 기초 (0) | 2022.11.17 |
WEB | DevTools (Network Panel) (0) | 2022.01.06 |
Web | Semantic Web (0) | 2022.01.04 |
Web | HTTP (0) | 2021.12.24 |