...
CORS 허용 설정 하는 방법
Node.js 서버 프로젝트에서 cors(cross origin resource sharing) 문제를 해결하는 방법은 크게 2가지가 있다.
하나는 직접 헤더를 명시해서 출처(origin)을 필터링하는 것이고, 다른 하나는 cors 노드 패키지를 사용해 좀더 간편하면서 유기적으로 출처를 필터링 할수 있다.
1. 직접 헤더에 명시
CORS 문제를 해결하기 위해서는 응답 헤더에 Access-Control-Allow-Origin 헤더를 넣어야 한다.
이 헤더는 클라이언트 도메인의 요청을 허락하겠다는 뜻을 가지고 있다.
//^ CORS 허용
res.setHeader('Access-Control-Allow-origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true'); // 쿠키 주고받기 허용
res.end();
위 처럼 res.set 메서드로 직접 넣어도 좋지만 활용성이 떨어지기에, npm에는 편하게 설치할 수 있는 패키지가 있다.
2. cors 미들웨어 사용
> npm i cors
const express = require('express')
const cors = require('cors');
const app = express();
app.use(cors({
origin: '*', // 모든 출처 허용 옵션. true 를 써도 된다.
}));
위와 같이 사용해주면 모든 라우터에 적용이된다.
이제 응답에 Access-Control-Allow-Origin 헤더가 자동으로 추가되어 나간다.
위 사항을 적용하고 클라이언트 사이드에서 응답 헤더를 보면, 기존에는 안 적혀있었던 Access-Control-Allow-Origin이 *로 되어있는 것을 볼 수 있다.
쿠키 요청 허용
fetch('http://localhost:3001/cors', {
method: 'PUT',
credentials: 'include' // credentials 옵션
})
.then(function(response) { ... })
.catch(function(error) { ... })
app.use(cors({
origin: true, // 출처 허용 옵션
credential: true // 사용자 인증이 필요한 리소스(쿠키 ..등) 접근
}));
만일 위와 같이 도메인이 다른 클라이언트가 쿠키 값을 보내고 싶으면, 클라이언트에선 credentials 옵션 설정을 넣어주어야 하고, 서버에서도 cors 설정에서 credentials: true 로 옵션을 둘다 설정해주어야 한다.
credentials: true 라는 옵션은 다른 도메인 간에 쿠키 공유를 허락하는 옵션이다. 서버 간 도메인이 다른 경우 이 옵션을 활성화하지 않으면 로그인되지 않을 수 있다.
참고로 axios에서도 도메인이 다른데, 쿠키를 공유해야 하는 경우 withCredentials: true 옵션으로 요청을 보내야 한다. 참고 링크
만일 credentail: true로 인증된 요청을 사용할 경우, Access-Control-Allow-Origin 값이 '*' 일 경우 다음 에러가 발생한다.
Access to XMLHttpRequest at 'http://lahuman.github.io' from origin 'http://localhost:8080' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
요약 하자면, 인증 정보를 포함한 통신시 Access-Control-Allow-Origin 값이 '*' 일 경우 지원을 하지 않는 다는 것이다. 그래서 credentail옵션을 쓸때는 반드시 허용 출처를 *가 아닌 직접 명시를 해주어야 한다.
특정 주소만 허용하기
그러나, 모든 CORS 요청을 허용해주는 것이 때로는 보안상의 문제로 이어질 수 있다.
솔직히 안쓴는 것만 못하다.
따라서, 특정 호스트만 CORS 요청을 허용해주고 싶다면 다음과 같이 whitelist를 추가해서 검증하는 방법이 있다.
요청을 허용할 주소를 담은 whitelist 배열을 생성해주고, 요청 HTTP 헤더의 Origin 속성을 확인해서 일치하는 항목이 있을 경우 허용해주는 방식이다.
const whitelist = ["http://localhost:8080", "http://localhost:8081"];
const corsOptions = {
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) { // 만일 whitelist 배열에 origin인자가 있을 경우
callback(null, true); // cors 허용
} else {
callback(new Error("Not Allowed Origin!")); // cors 비허용
}
},
};
app.use(cors(corsOptions)); // 옵션을 추가한 CORS 미들웨어 추가
혹은, 단순 배열이 아닌 데이터베이스로 체계적으로 허용된 도메인을 관리한다면, DB를 조회해서 허용된 도메인이라면 pass하게 설정 할 수 있다.
- DB에서 등록된 도메인인지 조회
- 만약 등록된 도메인이라면, 클라이언트에서 보낸 origin을 허용하게 설정 origin : req.get('origin')
//* cors 전용 라우터
router.use(async (req, res, next) => {
// api서버로부터 발급받은 허용된 사용자인지 DB로 체크
const domain = await Domain.findOne({
where: { host: new URL(req.get('origin'))?.host }, // 브라우저에서 요청 헤더에 origin을 넣어서 보낸걸 받는다.
});
if (domain) {
// 만약 api서버로부터 발급받은 허용된 사용자라면, cors 설정해주기
cors({
origin: req.get('origin'), // origin : true 모두 허용하면 보안상 위험하니까
credentials: true, // 쿠키 통신 설정 : 인증된 요청
})(req, res, next); //? 미들웨어 확장 패턴 : 그냥 router.use(cors()) 이렇게 쓰지말고 조건에 따라 미들웨어가 실행괴게 할 수 있다.
} else {
next(); // 만일 등록된 도메인이 아니라면 그대로 보내줘서 cors 에러뜨게 놔둔다.
}
});
Proxy 서버
CORS 문제를 해결하는 또 다른 방법으로 프록시(대리인) 서버를 사용하는 것이 있다.
서버에서 서버로 요청을 보낼 때는 CORS 문제가 발생하지 않는다는 것을 이용한 방법이다.
브라우저 -> 백엔드 서버로 통신하기 위해서 중간에 프론트 서버를 넣는 방식이다.
(브라우저가 아닌 서버와의 통신 또한 Cors정책 위반이 아니다)
[프록시 서버 구현 방식]
- 브라우저와 도메인이 같은 서버를 만든 후,
- 브라우저에서는 API 서버 대신 프록시 서버에 요청을 보낸다.
- 그 후, 프록시 서버에서 요청을 받아 다시 API 서버로 요청을 보낸다.
- 이는, 서버-서버 간의 요청이므로 CORS 문제가 발생하지 않게 된다.
프록시 서버는 직접 구현할 수도 있지만,
http-proxy-middleware 같은 패키지를 사용하면 쉽게 익스프레스와 연동할 수 있다.
[proxy 설정 2가지 방법]
- 클라이언트의 package.json 파일에 proxy: host:portNumber를 작성
- http-proxy-middleware 패키지 사용
https://create-react-app.dev/docs/proxying-api-requests-in-development
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.