위의 사진처럼 웹 개발을 하다보면 가끔 cors 정책 위반으로 인해 에러가 발생하는 경험이 있을거에요
cors error 가 프론트에서 발견하지만 보통 백엔드에서 해결해줘야 하는 에러라고 생각해서 그동안 잘모르고 지나갔었는데요
단순 서버에서의 에러인 줄 았았는데 브라우저의 에러라는 것을 알게 돼서 프론트엔드 개발자라면 cors 에 대해 개념정도는 알아야 하지 않나 싶어서 스터디 하게 되었습니다.
보통 cors 에러가 나타날때 " 다른 출처를 막는게 cors 구나" 라고 생각했었는데 알고보니 다른 출처 요청을 허용해주는게 cors 더라구요 .
한번 제대로 알아봅시다.
자바스크립트에서의 요청은 기본적으로 서로다른 도메인에 대한 요청을 보안상 제한해요.
여기서, 두가지 정책이 있는데 same origin 정책과 cross origin 정책이 있습니다.
이러한 정책들이 뭐길래 웹 브라우저가 외부 리소스를 가려서 받을까요?
웹 생태계에는 다른 출처로의 리소스 요청을 제한하는 것과 관련된 두 가지 정책이 존재한다.
SOP : Same-Origin Policy
CORS : Cross-Origin Resource Sharing
SOP 란?
“같은 출처 끼리만 요청 보낼 수 있다”
Same-Origin Policy = 동일 출처 정책
먼저 SOP 에 대해 설명을 해보자면,
“같은 출처 끼리만 요청 보낼 수 있는 보안 방식" 인데요
여기서 출처가 뭔가 라고 보면 https://google.com 과 같은 url 을 의미하는데
url 은 아래 사진과 같이 protocol, host, path, queary string, fragment 로 구성되어 있어요
출처는 protocol 과 host, path 를 합쳐놓은 것이라고 합니다. 이 3개를 합쳐놓은 걸로 같은 출처인지 다른 출처인지 판단할 수 있는 거라고 보면 됩니다.
Https 가 protocol 이 되고, google.com 도메인이 host 되고, 뒤에 8000 같이 붙는게 포트번호가 됩니다
인터넷익스플로러 같은 경우는 뒤에 포트가 출처를 판단을 안해요 그래서 보안에 취약하다고 하네요
📌 출처(Origin) 란?
서버의 위치를 위미하는 https://google.com 과 같은 URL
- Protocol(Scheme) : http, https
- Host : 사이트 도메인
- Port : 포트 번호
- Path : 사이트 내부 경로
- Query string : 요청의 key와 value값
- Fragment : 해시 태그
출처(Origin) = Protocol + Host + Port 합친 것 의미
서버의 위치를 찾아가기 위해 필요한 가장 기본적인 것
출처 내의 포트 번호 생략 가능
- HTTP, HTTPS 프로토콜의 기본 포트 번호가 정해져 있기 때문
console.log(location.origin);
브라우저 개발자 도구의 콘솔에서 Location 객체가 가지고 있는 origin 프로퍼티에 접근함으로써 어플리케이션이 실행되고 있는 출처 확인 가능
🪝 http://localhost과 같은 url 은?
- https://localhost
- http://localhost:80
- http://127.0.0.1
- http://localhost/api/cors
정답은 2번, 4번 입니다!
1번은 https 죠 보기와 프로토콜이 다릅니다
2번은 http 기본 port 가 80port 이기 때문에 보기에서는 생략된거고 둘은 동일 출처입니다
3번은 사실 127.0.0.1 의 ip는 localhost 가 맞긴 맞는데 브라우저 입장에서는 이거를 string value 로 비교를 한다고 해요
3번도 정답인 줄 알았는데 아니였네요 🙊
-> chat gpt의 답변입니다
4번은 api/cors 는 추가적으로 붙는 로케이션 이라서 api 앞에까지만 비교해서 이것은 동일 출처라고 볼 수 있습니다.
정리해서, sop 는 "다른 출처의 리소스를 사용하는 것에 제한 하는 보안 방식" 이라고 볼 수 있습니다.
그럼 왜 동일 출처가 아닌 경우 접근을 차단하는 이유는 뭘까요?
동일 출처가 아닌 경우 접근을 차단하는 이유는??
제약 없다면 -> CSRF(Cross-Site Request Forgery) , XSS(Cross-Site Scripting) 등의 방법으로 해킹 당함
출처 비교와 차단은 브라우저가 한다 !
출처를 비교하는 로직은 "브라우저에 구현된 스펙"
결국 CORS Error 는 브라우저의 SOP 정채에 따라 다른 출처의 리소스를 차단하면서 발생된 에러이고, CORS는 다른 출처의 리소스를 얻기위한 해결 방안 이었다.
=>SOP 정책을 위반해도 CORS 정책에 따르면 다른 출처의 리소스라도 혀용 한다는 뜻
출처가 다른 두 어플리케이션이 자유로이 소통할 수 있는 환경을 꽤 위험한 환경인데요
만약 제약이 없다면 해커가 CSRF 나 XSS 등으로 해커가 심어놓은 코드가 실행하게 해서 개인 정보를 가로챌 수 있습니다.
따라서, 이러한 악의적인 경우를 방지하기 위해, SOP 정책으로 동일하지 않은 다른 출처의 스크립트가 실행되지 않도록 브라우저에서 사전에 방지하는 것입니다.
! 그렇다면 다른 출처의 리소스가 필요하다면 어떻게 할까요? 바로 cors 로 해결합니다
cors란?
“다른 출처 라도 요청 보낼 수 있다”
Cross-Origin Resource Sharing
교차 출처 자원 공유
교차 출처라고 하면 잘 안와닿는데 한국어에 맞게 쉽게 말해서, "다른 출처"의 자원을 공유한다 라고 생각하면 됩니다
제가 이해한거는 cors 는 다른 출처에 접근을 한다는 것이고 이 권한은 브라우저에 알려주는 체제라고 이해했습니다.
cors 접근제어 시나리오는 3가지가 있는데
simple request / preflight request / credentialed request 가 있습니다
cors 접근제어 시나리오 (3)
- simple request
- preflight request
- credentialed request
1️⃣[단순 요청 Simple request]
1. 클라이언트에서 HTTP 요청의 헤더에 Origin 을 담아 전달
기본적으로 웹은 HTTP 프로토콜을 이용하여 서버에 요청을 보내는데, 브라우저는 요청 헤더에 Origin 이라는 필드에 출처를 담아 보낸다.
2. 서버는 응답헤더에 Access-Control-Allow-Origin 을 담아 클라이언트로 전달
이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더에 Access-Control-Allow-Origin 이라는 필드를 추가하고 값으로 '이 리소스를 접근하는 것이 허용된 출처 url' 을 내려보낸다
3. 클라이언트에서 Origin 과 서버가 보내준 Access-Control-Allow-Origin 을 비교한다.
만약 유효하지 않으면 그 응답을 버린다 (CORS 에러)
결국 CORS 해결책은 서버의 허용이 필요
서버에서 Access-Control-Allow-Origin 헤더에 허용할 출처를 기재해서 클라이언트에 응답하면 됨 <- 백엔드 개발자가 고쳐야 함
[ Simple Request 조건]
HTTP method 가 다음 중 하나이면서
- GET
- HEAD
- POST
자동으로 설정되는 헤더는 제외하고, 설정할 수 있는 다음 헤더들만 변경하면서
- Accept
- Accept-Language
- Content-Language
Content-Type 이 다음과 같은 경우
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
Simple request 라고 부른다.
이 요청은 추가적으로 확인하지 않고 바로 본 요청을 보낸다.
2️⃣[✈️ 예비 요청 Preflight Request ]
사실 브라우저는 요청을 보낼 때 한번에 바로 보내지 않고, 먼저 예비 요청을 보내 서버와 잘 통신되는지 확인 후 본 요청을 보낸다
예비 요청 = 본 요청 보내기 전 브라우저 스스로 안전한 요청인지 미리 checking
예비 요청의 HTTP 메소드를 GET 이나 POST 가 아닌 OPTIONS 라는 요청이 사용된다
Simple requests가 아닌 cross-origin요청은 모두 preflight 요청을 하게 되는데, 실제 요청을 보내는 것이 안전한지 확인하기 위해 먼저 OPTIONS 메서드를 사용하여 cross-origin HTTP 요청을 보냅니다.
이렇게 하는 이유는 사용자 데이터에 영향을 미칠 수 있는 요청이므로 사전에 확인 후 본 요청을 보냅니다.
[ preflight request 조건]
요청 헤더 목록
- Origin
- Access-Control-Request-Method
- preflight 요청을 할 때 실제 요청에서 어떤 메서드를 사용할 것인지 서버에게 알리기 위해 사용됩니다.
- Access-Control-Request-Headers
- preflight요청을 할 때 실제 요청에서 어떤 header를 사용할 것인지 서버에게 알리기 위해 사용됩니다.
응답 헤더 목록
- Access-Control-Allow-Origin
- 브라우저가 해당 origin이 자원에 접근할 수 있도록 허용합니다. 혹은 ``은 credentials이 없는 요청에 한해서 모든 origin에서 접근이 가능하도록 허용합니다.
- Access-Control-Expose-Headers
- 브라우저가 액세스할 수 있는 서버 화이트리스트 헤더를 허용합니다.
- Access-Control-Max-Age
- 얼마나 오랫동안 preflight요청이 캐싱 될 수 있는지를 나타낸다.
- Access-Control-Allow-Credentials
- Credentials가 true 일 때 요청에 대한 응답이 노출될 수 있는지를 나타냅니다.
- preflight요청에 대한 응답의 일부로 사용되는 경우 실제 자격 증명을 사용하여 실제 요청을 수행할 수 있는지를 나타냅니다.
- 간단한 GET 요청은 preflight되지 않으므로 자격 증명이 있는 리소스를 요청하면 헤더가 리소스와 함께 반환되지 않으면 브라우저에서 응답을 무시하고 웹 콘텐츠로 반환하지 않습니다.
- Access-Control-Allow-Methods
- preflight`요청에 대한 대한 응답으로 허용되는 메서드들을 나타냅니다.
- Access-Control-Allow-Headers
- preflight요청에 대한 대한 응답으로 실제 요청 시 사용할 수 있는 HTTP 헤더를 나타냅니다.
왜 preflight request 가 필요한가??
⇒ CORS 를 모르는 서버를 위해서 이다
만약 서버가 cors 설정이 없는 서버라면 , 클라이언트에서 본요청을 바로 보냈을 때 어떤 오류가 날까?
cors 가 교차출처요청 이라고 했잖아요
만약 서버가 cors 에 관해 아무런 설정이 없는 서버라고 해봅시다
클라이언트에서 본요청을 바로 보냈을 때 어떤 오류가 생기냐면
클라이언트 입장에서는 나의 origin 이라고 브라우저에게 보내게 되고
브라우저는 서버에게 그대로 보냅니다
서버는 cors 에 대해서 전혀 모르기 때문에 그냥 일단 해결을 합니다 응답을 내리게 되는데
서버는 cors 설정이 없기 때문에 allow-orgin 같은 경우가 없는데
브라우저는 그것을 확인하고 그제서야 서버한테 allow-origin 이 없다고 이거는 cors 에러야 라고 이야기 해줍니다
여기서 서버는 일단 다 해결했어요 응답을 했는데 브라우저에서 CORS 에러를 내뱉는 겁니다
이 요청이 심플한 get 요청이 아니라 delete 같은 요청이라면
서버입장에서는 db 를 다 지우고 응답을 하게 되는데 브라우저에서는 뒤늦게 cors error 라고 터지는데 늦었다는거죠
그래서 preflight 가 필요하다는 겁니다
preflight 는
클라이언트가 요청을 브라우저에 보내고 브라우저는 그대로 서버에 보내게되죠
서버입장에서는 cors 설정이 없으니 당연히 allow-origin 이 없죠 그래서 브라우저는 cors error 를 터트립니다
이거는 사전요청이기 때문에 서버는 행동을 하지 않아요
그래서 클라이언트에서는 이거는 cors error 이니까 다음 실제요청을 보내지 않게 되죠
그렇기 때문에 서버는 안전하게 잘 지켜집니다
다시정리하면 preflight 는 cors 를 모르는 서버를 위해서 필요한 작업이다 ! 라고 이해하시면 될 것 같아요
3️⃣ [🔐인증 정보 포함 요청 Credentialed Request]
인증 관련 헤더를 포함할 때 사용하는 요청
쿠키나 jwt 토큰 을 클라이언트에서 자동으로 담아서 보내고 싶을 때 즉 , 다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법이에요.
[credentialed request 조건]
클라이언트 측
- credentials: include
서버 측
- Accelss-Control-Allow-Credentials : true
- (Access-Control-Allow-Origin: * 안된다)
해결법?
서버에서 Access-Control-Allow-Origin 헤더 세팅하기
정리
origin 즉 출처를 비교하는 로직은 서버에 구현된 스펙이 아니라 브라우저에 구현된 스펙이다.
CORS(Cross-Origin Resource Sharing)는 웹 애플리케이션에서 다른 출처(origin)로부터 온 리소스에 접근할 때 발생하는 보안 정책 이고,
CORS Error(Cross-Origin Resource Sharing Error)란, 웹 브라우저에서 보안상의 이유로 다른 출처(origin)로부터의 리소스 요청을 거부하는 오류를 의미한다.
"SOP 정책때문에 막혔던 다른 출처로의 요청을 CORS 설정을 거치게 되면 정상적으로 요청을 보낼 수 있게 된다 ! ”
'frond-end' 카테고리의 다른 글
로그아웃 시 다른 에러 상태 코드 도 같이 보여주는 문제 (axios + vue3) (0) | 2024.03.07 |
---|---|
[CSS] z-index 의 동작방식 (1) | 2024.03.04 |
[typescript] any VS unknown (0) | 2024.02.21 |
TDD (Test-Driven Development) (0) | 2023.09.02 |
GitHub 사용법 (0) | 2022.02.17 |