이번 프로젝트에서는 한 페이지 내에 데이터를 여러 번 불러와야 했다. 그러다가 간헐적으로 검은 화면이 뜨면서 데이터가 로딩되지 않는 에러가 발생했고, CORS 에러임을 확인할 수 있었다.
CORS 에러에 대해서 찾아보니, 많이들 두려워하는 에러이기도 하고, 서버 차원에서 해결해야 하는 문제라고 해서 이번 프로젝트에서 해결했던 방법과 CORS 에러에 대해 이야기해보려고 한다.
CORS (Cross-Origin Resource Sharing) 에러는 웹 애플리케이션이 다른 도메인에 있는 리소스를 요청할 때 발생할 수 있는 보안 기능이다. 브라우저는 보안상의 이유로, 웹 페이지에서 자바스크립트를 사용해서 자신이 로드된 도메인과 다른 도메인의 리소스에 접근하는 걸 기본적으로 금지하고 있으며 보안 기능은 SOP (Same-Origin Policy, 동일 출처 정책)라고 한다.
동일 출처 정책 (Same-Origin Policy, SOP)
SOP는 웹 브라우저의 기본 보안 정책으로, 한 출처(origin)에 로드된 웹 페이지가 다른 출처의 리소스에 접근하는 것을 제한하는 정책이다. 여기서 출처는 다음 세 가지 요소로 구성되는데,
프로토콜 (ex: http, https)
도메인 (ex: example.com)
포트 (ex: 80, 443)
동일 출처 정책의 목표는 악의적인 웹 페이지가 다른 출처의 민감한 데이터를 훔치거나 조작하는 것을 방지하는 것이다.
예를 들어, http://sprint.com 에서 로드된 웹 페이지가 http://api.yellojmain.com 에 요청을 보내면, 이 요청은 CORS 정책에 의해 차단될 수 있다.
서버가 CORS 헤더를 설정하지 않거나 잘못 설정한 경우 CORS 요청이 차단된다. CORS 요청이 허용되기 위해서는 서버가 Access-Control-Allow-Origin
헤더를 통해 특정 도메인 또는 모든 도메인을 명시적으로 허용해야 한다.
브라우저는 GET, POST 이외의 HTTP 메소드(예: PUT, DELETE)나 커스텀 헤더를 사용하는 요청에 대해 CORS 검사를 더 엄격하게 수행하는데, 이 경우 서버는 Access-Control-Allow-Methods
와 Access-Control-Allow-Headers
를 통해 허용된 메소드와 헤더를 명시해야 한다.
사실, CORS 에러는 서버 차원에서 수정해야 하는 거라, 우리가 근본적인 문제를 해결할 수 없었다. 하지만 데이터를 불러오며 발생하는 거였기 때문에 api 요청에 있어서 우리가 할 수 있는 방법을 생각해보다가 retry 훅을 만들어 사용하기로 했다.
(하지만 이 방법은 임시방편으로 해결한 것이라서 다른 프로젝트에는 적용이 되지 않을 수 있다는 점을 유의해주셨으면 좋겠습니다!!)
useAsyncWithRetry
란 이름의 커스텀 훅을 만들어서, 비동기 함수를 실행하면서, 오류가 발생하면 지정된 횟수만큼 재시도하였고, 아래에서와 같이 사용하였다.
하지만, 데이터를 여러 번 요청해서 화면에 데이터를 불러오는 것은 임시방편에 불과하고 근본적으로 문제를 해결한 것은 아니기 때문에, CORS 에러를 클라이언트 측에서와 서버 측면에서 해결하는 방법에 대해 이야기해보려고 한다.
프록시 서버를 설정하여 클라이언트와 대상 서버 사이에 중계 역할을 하도록 한다. 클라이언트는 동일 출처인 프록시 서버에 요청을 보내고, 프록시 서버는 대상 서버에 요청을 전달한다.
fetch('http://localhost:5000/proxy?url=http://api.anotherdomain.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
CORS 정책을 무시하도록 하는 브라우저 확장 프로그램을 사용한다. 하지만 이는 개발 환경에서만 사용해야 하며, 보안상 프로덕션 환경에서는 절대 사용하면 안 된다.
예시: Chrome의 Acces-Control-Allow-Origin
확장 프로그램
크롬 확장 프로그램 설치 링크
서버 측에서 적절한 CORS 헤더를 추가하여 특정 도메인이나 모든 도메인에서의 요청을 허용하도록 설정한다.
const velog = require('velog');
const app = velog();
app.get('/api', (req, res( => {
res.header("Access-Control-Allow-Origin", "허용하고자 하는 도메인");
res.send(data);
});
하지만, Access-Control-Allow-Origin: *
이런 식으로 작성하는 것은 모든 출처에서 오는 요청을 허용하는 것이기 때문에 지양해야 한다.
서버를 Express 로 구축한 경우 Node.js 미들웨어 중 하나인 CORS 를 사용하여 문제를 간단하게 해결할 수 있다.
const express = require('express');
const cors = require('cors');
const app = express();
const corsOptions = {
origin: '허용하고자 하는 도메인',
};
app.use(cors(corsOptions));
origin에 허용하고자 하는 도메인을 넣어주면 response 헤더에 Access-Control-Allow-Origin 내용이 추가된다.
app.use(cors()) 이렇게 사용하면 위에서와 같이 모든 출처에서 오는 요청을 허용하는 것이기 때문에 지양해야 한다.
안녕하세요 글 잘 읽었습니다!
올려주신 사진 중에 백엔드 주소가 노출된 사진이 있어 해당 내용 가려주셔야 안전할 것 같아요!
그리고 글의 중반부에 정석적이진 않지만 해결하시게 된 방법에 API를 n번 request를 다시 보내어 해결한 내용이 있는데, 이는 보편적인 상황에서의 해결책으로는 작동하지 않을 확률이 높다고 생각됩니다! 특수 상황임을 글에 작성해주시면 추후 CORS를 위해 해당 글을 참고하실 분이 헷갈리시지 않을 것 같다고 생각이 들어요!