CORS 에러란? (w. 해결방법)
CORS(Cross-Origin Resource Sharing)
- 다른 출처의 리소스 공유에 대한 허용/비허용 정책
- 한 출처에 있는 리소스에서 다른 출처에 있는 리소스에 안전하게 접근하도록 하는 메커니즘
출처란?
- URL의 구성 요소 중
프로토콜 + host + port
3가지가 같으면origin
(동일 출처)라고 하며, 이 중 하나라도 다르다면other origin
(다른 출처)이라고 한다.
ex)
1. http://example.com/app1 2. https://example.com/app2 |
프로토콜이 다름 |
1. http://example.com 2. http://www.example.com 3. http://myapp.example.com |
host가 다름 |
1. http://example.com 2. http://example.com:8080 |
80, 8080 포트가 다름 (기본 http 포트: 80) |
요청 방식에 따라 다른 CORS 발생 여부
1. <img>
, <script>
, <video>
, <link>
태그 등
<link rel="stylesheet" href="…" />
<script src="…"></script>
<img src="…" />
- 기본적으로 Cross-Origin 정책을 따른다.
<link>
태그의href
속성에서 다른 사이트의 .css 리소스에 접근하는 것이 가능<img>
태그의src
에서 다른 사이트의 .png, .jpg 등의 리소스에 접근하는 것이 가능<script>
태그의src
에서 다른 사이트의 .js 리소스에 접근하는 것이 가능 (type=”module”은 제외)
2. XMLHttpRequest
, Fetch API
스크립트
- 기본적으로 Same-Origin 정책을 따른다.
- 자바스크립트에서의 요청은 기본적으로 서로 다른 도메인에 대한 요청을 보안상 제한한다. 브라우저는는 기본으로 하나의 서버 연결만 허용되도록 설정되어 있기 때문이다.
SOP(Same-Origin Policy)가 왜 필요할까?
→ 다른 출처 요청의 위험성
- SOP 정책이 없어 서로 다른 출처의 두 어플리케이션이 자유롭게 소통할 수 있는 환경이 된다면, 악성 사이트에서 원래 사이트를 흉내내어 사용자가 로그인을 하도록 유도하고, 로그인했던 세션을 악의적으로 이용해 사용자 정보를 탈취하는 등의 공격을 할 수 있다.
- 따라서 SOP 정책으로 동일하지 않은 출처의 스크립트가 실행되지 않도록 브라우저에서 사전에 방지한다. 즉, 다른 출처로부터의 공격을 예방하기 위한 보안상의 이유!
CORS 에러란?
- 위와 같이 SOP 정책에 따라 다른 출처의 리소스를 차단하면서 발생한 에러를 CORS 에러라고 한다.
ex) 같은 외부 image를 <img> 태그와 ajax 요청으로 가져오기
fetch('<<a href=https://third-party-test.glitch.me/check.svg>https://third-party-test.glitch.me/check.svg</a>>')
.then(response => response.blob())
.then(imgBlob => {
const imageObjectURL = URL.createObjectURL(imgBlob); // 응답 받은 이미지를 blob 객체로 변환
const img = document.createElement('img'); // 이미지 태그를 생성하고
img.src = imageObjectURL; // 이미지 경로를 설정한뒤
document.body.append(img); // html에 추가
})
⇒ <img> 태그로 이미지를 잘 가져오지만, ajax 요청에선 CORS error가 발생
💡 참고로 이러한 출처 비교 & 차단은 '브라우저'에서 이뤄진다. 즉 서버 단에서의 다른 출처 요청은 CORS 에러에 자유로운데, 그래서 이를 이용한 Proxy(프록시) 서버라는 것도 있다.
브라우저의 CORS 동작 과정
다음은 기본적인 동작 흐름이며 이외에 여러 동작 방식들이 있다. (아래 참고)
- 클라이언트 → 서버로 http 요청을 보낼 때, 헤더에
Origin
을 담아 전달한다. 브라우저는 요청 헤더에 Origin이라는 필드에 자신의 출처를 함께 담아 보낸다. - 서버는 응답 헤더에
Access-Control-Allow-Origin
을 담아 클라이언트로 전달한다. 이 때 담기는 값은 ‘이 리소스를 접근하는 것이 허용된 출처 url’이다. - 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의
Access-Control-Allow-Origin
을 비교한다.
- 만약 유효하지 않다면 그 응답을 사용하지 않고 버림 → CORS 에러 발생
- 유효할 경우 다른 출처의 리소스를 문제없이 가져온다.
💡 CORS가 동작하는 방식들
1. Simple Request(단순 요청)
- 예비 요청(preflight)을 생략하고 바로 서버에 본 request를 보낸 뒤, 서버가 이에 대한 response 헤더에 Access-Control-Allow-Origin 헤더를 보내주면 브라우저가 CORS 정책 위반 여부를 검사하는 방식
- 단순 요청인 만큼, 요청이 가능한 조건이 까다로운데, 아래의 조건들이 있다.
(1) 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
(2) Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width 헤더일 경우에만 적용된다.
(3) Content-Type 헤더가 application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나여야한다. 아닐 경우 예비 요청(preflight)으로 동작된다.
- 위의 조건을 모두 만족해야 simple request가 일어나기 때문에 이 상황은 드물게 일어난다고 한다. 왜냐하면 대부분의 Http API 요청은
text/xml
또는application/json
으로 통신하기 때문이다.
2. Preflight Reqeust(예비 요청)
OPTIONS
메소드로 http 요청을 미리 보내 실제 요청을 전송하기에 안전한지 확인한다.
3. Credentialed Request(신용 요청)
- 쿠키, 인증 헤더, TLS 클라이언트 인증서 등의 신용 정보와 함께 요청한다.
CORS 에러 해결 방법
⇒ 결론적으로, CORS 해결책은 서버의 허용이 필요하다.
- 서버에서
Access-Control-Allow-Origin
값에 허용할 출처를 기재해서 클라이언트에 응답하면 된다. 즉 백엔드 쪽에서 고쳐야할 부분 (참고로 클라이언트에서 자바스크립트로 origin 헤더값을 조작해도, 브라우저에서 이를 감지하여 차단하므로 불가능하다.)
1. Chrome 확장 프로그램 이용하기 Allow-CORS
- 로컬 환경에서 API 테스트할 때 CORS 에러를 해결할 수 있다.
2. Proxy(프록시) 사이트 이용하기
- 클라이언트에서 직접 서버에 리소스를 요청했을 때, 서버에서 따로 설정을 안해줘서 CORS 에러가 뜬다면, 모든 출처를 허용하는 서버 대리점을 통해 요청을 할 수 있다.
- 즉 프록시는 클라이언트-서버 사이의 중계 대리점이라고 보면 된다.
- 그러나 이 또한 악용 사례가 있으므로 api 요청 횟수 제한을 두기 때문에 테스트용 등으로 간단하게 사용할 수 있다. 실제 사용 시, 직접 프록시 서버를 구축해서 사용해야 한다.
3. 서버에서 Access-Control-Allow-Origin
헤더를 세팅하기(정석적인 해결책)
- 직접 서버에서 Http 헤더 설정을 통해 출처를 허용하게 설정한다.
- 서버는 Node.js, Spring 등 여러가지가 있어, 각 서버의 문법에 맞게 설정한다.
ex) Node.js 서버
var http = require('http');
const PORT = process.env.PORT || 3000;
var httpServer = http.createServer(function (request, response) {
// Setting up Headers 👈
response.setHeader('Access-Control-Allow-origin', '*'); // 모든 출처(orogin)을 허용
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); // 모든 HTTP 메서드 허용
response.setHeader('Access-Control-Allow-Credentials', 'true'); // 클라이언트와 서버 간에 쿠키 주고받기 허용
// ...
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('ok');
});
httpServer.listen(PORT, () => {
console.log('Server is running at port 3000...');
});
- 위의 코드에선 모든 출처를 허용하게 설정했지만, 모든 Origin에서 오는 요청을 허용하기 때문에 그만큼 위험성이 증가하므로, 웬만해서 출처를 직접 명시해주기
Reference
🌐 악명 높은 CORS 개념 & 해결법 - 정리 끝판왕 👏
악명 높은 CORS 에러 메세지 웹 개발을 하다보면 반드시 마주치는 멍멍 같은 에러가 바로 CORS 이다. 웹 개발의 신입 신고식이라고 할 정도로, CORS는 누구나 한 번 정도는 겪게 된다고 해도 과언이
inpa.tistory.com
CORS란 무엇인가?
개요 웹 프로그래밍에서 프런트와 백엔드 작업을 하면, 한번씩 발생하는 문제가 CORS 문제입니다. 현재 하는 업무가 이런 경우는 없었지만, 개인 프로젝트 시 발생했던 문제를 기억하며 해당 문
escapefromcoding.tistory.com
CORS란 무엇인가? – Yunseok's Dev Blog
hannut91.github.io