본문 바로가기

clone toy projects/node_express_sns

CORS 이해하기

이전 절에서 NodeCat이 nodebird-api를 호출하는 것은 서버에서 서버로 API를 호출한 것입니다. 

만약 NodeCat의 프론트에서 nodebird-api의 서버 API를 호출하면 어떻게 될까요? 

routes/index.js에 프론트 화면을 렌더링하는 라우터를 추가합니다.

프론트 화면도 추가

 

clientSecret의 {{key}} 부분이 Nunjucks에 의해 실제 키로 치환되어 렌더링됩니다. 

 

단, 실제 서비스에서는 서버에서 사용하는 비밀 키와 프론트에서 사용하는 비밀 키를 따로 두는 것이 좋습니다. 

보통 서버에서 사용하는 비밀 키가 더 강력하기 때문입니다. 

프론트에서 사용하는 비밀 키는 모든 사람에게 노출된다는 단점도 따릅니다. 

 

데이터베이스에서 clientSecret 외에 frontSecret 같은 컬럼을 추가해서 따로 관리하는 것을 권장합니다. 

http://localhost:4000에 접속하면 에러가 발생하며 제대로 동작하지 않습니다. 

브라우저 콘솔 창을 보면 에러를 확인할 수 있습니다.

"Access-Control-Allow-Origin" 이라는 헤더가 없다는 내용의 에러입니다. 

이처럼 브라우저와 서버의 도메인이 일치하지 않으면 기본적으로 요청이 차단됩니다. 

 

이 현상은 브라우저에서 서버로 요청을 보낼 때만 발생하며, 서버에서 서버로 요청을 보낼 때는 발생하지 않습니다

현재 요청을 보내는 클라이언트 (localhost:4000)와 요청을 받는 서버 (localhost:8002)의 도메인이 다릅니다. 

이 문제를 CORS (Cross-Origin Resource Sharing) 문제라고 부릅니다.

Network 탭을 보면 Method가 POST 대신 OPTIONS로 표시됩니다. 

OPTIONS 메서드는 실제 요청을 보내기 전에 서버가 이 도메인을 허용하는지 체크하는 역할을 합니다.

 

 

No deBi rd API 서버콘솔에도 OPTIONS요청이기록됩니다.

 

CORS 문제를 해결하기 위해서는 응답 헤더에 Access-Control-Allow-Origin 헤더를 넣어야 합니다. 

이 헤더는 클라이언트 도메인의 요청을 허락하겠다는 뜻을 가지고 있습니다. 

 

res.set 메서드로 직접 넣어도 되지만, npm에는 편하게 설치할 수 있는 패키지가 있습니다. 

바로 cors입니다. 응답 헤더를 조작하려면 NodeCat이 아니라 NodeBird API 서버에서 바꿔야 합니다. 

응답은 API 서버가 보내는 것이기 때문입니다. NodeBird API에 cors 모듈을 설치하면 됩니다.

 

npm i cors

 

 

 

router.use로 V2의 모든 라우터에 적용했습니다. 

이제 응답에 Access-Control-Allow-Origin 헤더가 추가되어 나갑니다. 

 

credentials: true 라는 옵션도 주었는데, 이 옵션을 활성화해야 다른 도메인간에 쿠키가 공유됩니다. 

서버간의 도메인이 다른 경우에는 이 옵션을 활성화하지 않으면 로그인되지 않을 수 있습니다. 

참고로 axios에서도 도메인이 다른데, 쿠키를 공유해야 하는 경우 withCredentials: true 옵션을 줘서 요청을 보내야 합니다.

다시 http://localhost:4000에 접속해보면 토큰이 발급된 것을 볼 수 있습니다. 

이 토큰을 사용해서 다른 API 요청을 보내면 됩니다. 

 

응답 헤더를 보면 Access-Control-Allow-Origin이 *로 되어있습니다. *는 모든 클라이언트의 요청을 허용한다는 뜻입니다. 

credentials: true 옵션은 Access-Control-Allow-Credentials 헤더를 true로 만듭니다. 

하지만 이것 때문에 새로운 문제가 생겼습니다. 

 

요청을 보내는 주체가 클라이언트라서 비밀 키 (process.env.CLIENT_SECRET)가 모두에게 노출됩니다. 

방금 CORS 요청도 허용했으므로 이 비밀 키를 가지고 다른 도메인들이 API 서버에 요청을 보낼 수 있습니다.

이 문제를 막기 위해 처음에 비밀 키 발급 시 허용한 도메인을 적게 설정했습니다. 

호스트와 비밀 키가 모두 일치할 때만 CORS를 허용하게 수정하면 됩니다.

먼저 도메인 모델로 클라이언트의 도메인 (req.get('origin'))과 호스트가 일치하는 것이 있는지 검사합니다. 

http나 https 같은 프로토콜을 떼어낼 때는 url.parse 메서드를 사용합니다. 

일치하는 것이 있다면 CORS를 허용해서 다음 미들웨어로 보내고, 일치하는 것이 없다면 CORS 없이 next를 호출합니다.

cors 미들웨어에 옵션인수를 주었는데요. 

origin 속성에 허용할 도메인만 따로 적으면 됩니다. *처럼 모든 도메인을 허용하는 대신 기입한 도메인만 허용합니다. 

여러 개의 도메인을 허용하고 싶다면 배열을 사용하면 됩니다. 

또한  cors 미들웨어 사용방식에 특이한 점이 하나 있습니다. 

9장의 passport.authenticate 미들웨어처럼 cors 미들웨어에도 (req, res, next) 인수를 직접 줘서 호출했습니다. 

이는 미들웨어의 작동 방식을 커스터마이징하고 싶을 때 사용하는 방법이라고 설명했습니다. 

다음 두 코드가 같은 역할을 한다는 것을 기억해두면 다양하게 활용할 수 있습니다.

다시 http://localhost:4000에 접속하면 성공적으로 토큰을 가져옵니다. 

응답의 헤더를 확인해보면 Access-Control-Allow-Origin이 http://localhost:4000으로 적용되어있습니다.

이렇게 특정한 도메인만 허용하므로 허용되지 않은 다른 도메인에서 요청을 보내는 것을 차단할 수 있습니다.

 

 


현재 클라이언트와 서버에서 같은 비밀키를 사용해서 문제가 될 수 있습니다. 

따라서 그림 10-19와 같이 다양한 환경의 비밀키를 발급하는 것이 바람직합니다. 

카카오의 경우 REST API 키가 서버용 비밀키이고, JavaScript 키가 클라이언트용 비밀키입니다.

 

 

 

Note. 프록시 서버

CORS 문제를 해결하는 또 다른 방법으로 프록시 (대리인) 서버를 사용하는 것이 있습니다.

서버에서 서버로 요청을 보낼 때는 CORS 문제가 발생하지 않는다는 것을 이용한 방법입니다.

위 그림처럼 브라우저와 도메인이 같은 서버를 만든 후브라우저에서는 API 서버 대신 프록시 서버에 요청을 보냅니다. 

그 후 프록시 서버에서 요청을 받아 다시 API 서버로 요청을 보냅니다. 

서버-서버 간의 요청이므로 CORS 문제가 발생하지 않습니다. 

프록시 서버는 직접 구현해도 되지만 npm에서 http-proxy-middleware 같은 패키지를 사용하면 쉽게 익스프레스와 연동할 수 있습니다.

 

 

 

• API는 다른 애플리케이션의 기능을 사용할 수 있게 해주는 창구입니다.

   현재 NodeCat이 NodeBird의 API를 사용하고 있습니다.


• 모바일 서버를 구성할 때 서버를 REST API 방식으로 구현하면 됩니다.

    API 사용자가 API를 쉽게 사용할 수 있도록 사용 방법, 요청 형식, 응답 내용에 관한 문서를 준비합니다.

• JWT 토큰의 내용은 공개되며 변조될 수 있다는 것을 기억합시다.

    단, 시그니처를 확인하면 변조되었는지 체크할 수 있습니다. 토큰을 사용하여 API의 오남용을 막습니다. 

   요청 헤더에 토큰이 있는지를 항상 확인하는 것이 좋습니다.

• app.use 외에도 router.use를 활용하여 라우터 간에 공통되는 로직을 처리할 수 있습니다.

• cors나 passport.authenticate처럼 미들웨어 내에서 미들웨어를 실행할 수 있습니다. 

   미들웨어를 선택적으로 적용하거나 커스터마이징할 때 이 기법을 사용합니다.

• 브라우저와 서버의 도메인이 다르면 요청이 거절된다는 특성(CORS)을 이해합시다. 

   서버와 서버 간의 요청에서는 CORS 문제가 발생하지 않습니다.

 

'clone toy projects > node_express_sns' 카테고리의 다른 글

유닛 테스트  (0) 2023.08.25
테스트 준비하기  (0) 2023.08.25
사용량 제한 구현하기  (0) 2023.08.25
SNS API 서버 만들기  (0) 2023.08.25
다른 서비스에서 호출하기  (0) 2023.08.25