React로 개발한 클라이언트 앱 HTTPS로 배포하니 라우터가 안되요.. 사용자 인증까지!

노요셉·2019년 10월 19일
7

코드스테이츠 이머시브 과정중 두가지 프로젝트를 진행했는데요.
그중에서 불과 10일만에 얼굴인식해서 눈코입을 트래킹하는 라이브러리를 연동하고, 배포를 해보면서
알아낸 것들을 정리하려고 합니다.

저희 프로젝트의 개념도는 다음과 같습니다.

최종적인 개념도는 다음과 같습니다.

image.png

블로그할 내용을 크게 다음과 같이 나눌 수 있었어요.

  • Face tracking 라이브러리 연동하기!
  • 로컬에서 개발해서 배포했을때 발생한 문제
  • S3로 정적호스팅을 하니까요. 라우터가 동작하질 않아요..
  • 사용자 인증 방법
  • 마지막으로 아쉬운 점 개선해야할 점

Face tracking 라이브러리 연동하기!

얼굴을 인식하고 68개의 landmarks로 눈 코 입에 대한 정보도 얻어올 수 있는
라이브러리를 연동하는 문제를 해결하는 것이 이 프로젝트의 시작이었습니다.
처음에는 어떻게 인식할건지에 대해서 opencv javascript without compile, opencv-node.js 등 다양한 방법을 찾았습니다.

가장 큰 문제는 주어진 시간은 토, 일 이라는 것이었고, 가장 이해하기 쉬운 라이브러리를 가져다 붙였습니다.
https://github.com/Tastenkunst/brfv4_javascript_examples

자바스크립트를 사용했기 때문에 어렵지 않게 붙일 순 있었습니다.
저희는 눈의 깜빡임을 인식하는게 목적이었기 때문에 깜빡이는 것을 어떻게 react에서 status로 관리하는지가 어려웠는데,
처음에는 커스텀 이벤트를 생성해서 했습니다. 그런데, 어차피 메모리에 로드되는건 매 한가지니까요.
그래서 리액트 컴포넌트 생성자호출시
window.reacMethod = this 이렇게 window에 컴포넌트를 바인딩 해줬습니다.
눈 깜박임 제어를 하게 되었습니다.
그리고 게임이 시작할때 스크립트로 구현되어있는 얼굴인식 모듈을 로드해줘야하기 때문에 cdn-promise라는 라이브러리를 통해 다음과 같이 FlappyBird라는 컴포넌트가 로드 될때 메모리에 웸캠 제어를 할 수 있게 되었습니다.

function loadScript() {
  return Promise.all([
    cdnPromise("/js/BRFv4Demo.js"),
    cdnPromise("/js/libs/createjs/preloadjs.min.js"),
    documentReady()
  ])
    .then(() => window.brfv4Example.start())
    .catch(err => {
      console.error(err);
    });
}

export default class FlappyBird extends Component {
  componentDidMount() {
    loadScript();
  }
  render() {
    return <div></div>;
  }
}

로컬에서 개발해서 배포했을때 발생한 문제

웹캠을 제어하는 로직이 있다면 http로 배포를 해도 크롬에서 웹캠을 제어할 수 없습니다.

처음 저희 개념도는 다음과 같았어요.

image.png

AWS S3의 정적 호스팅을 통해 배포한 클라이언트 앱을 사용자들에게 서비스를 제공 하려는데 문제가 생겼습니다.
로컬에서 테스트할때는 문제가 없었는데, 배포를 하니 웸캡 제어에서 쓰이는 메서드가 말썽을 부립니다.
getUserMedia()

image.png
Secure context required Yes 보이죠 https 쓰라는 말로 받아들여서 https를 써야한다고 생각했어요.

s3에 SSL 인증을 어떻게 적용하죠?

https를 어떻게 쓸껀데요? s3 https로 구글링하면 바로 CloudFront라는 녀석이 나옵니다.다들 cloudFront와 s3를 연동해서 쓴대요.

cloudFront 를 검색했어요. cloudFront의 역할은 CDN(캐싱(속도!), 지리적 이점(속도,부하), 부하 줄이기)이였어요.

그런데 거기에 SSL인증도 있어요.

CloudFront는 s3 버킷 앞에서 s3로 오는 네트워크 트래픽이 지나는 통로를 지켜요.

프로젝트 개념도를 다시 가져와서 클라이언트 부분만 다시 보면

image.png

아 그러면 https를 이제 연동하기 위한 작업을 할게요.

  1. aws certificate manager( ssl 인증서 발급처)에서 ssl 인증서 생성할게요.
    1-1. freenom에서 무료 도메인을 받아서 사용했습니다.
    1-2. 발급받은 도메인으로 AWS Certificate Manager로 SSL인증 받을 수 있습니다.
    여기에 나온대로 따라하시면 됩니다. 시도해본 것중 가장 친절하고, 빠른방법 입니다. Getting A SSL Certificate From AWS Certificate Manager - acloudxpert

  2. s3 정적 호스팅

  3. cloudFront - S3 연동
    S3와 CloudFront연동후, CloudFront에 SSL을 적용. 그리고 Custom Domain 설정.

AWS Certificate Manager에서 SSL인증을 받으셨다면 Custom SSL Certificate 활성화 시키면
SSL인증키가 리스트에 뜰거에요.
스크린샷 2019-10-22 오전 11.27.28_1.png

  1. Route53
    CloudFront에 배포한 웹호스팅 도메인과 Custom Domain을 연결.

레퍼런스 : 아마존 웹 서비스를 다루는 기술 12장 - 2. S3와 CloudFront 연동하기 - pyrasis

레퍼런스 : 무료 도메인 생성해서 AWS ROUTE53에 연동하는 방법 - 친절한 돌이
레퍼런스 : Route 53와 CloudFront 연동하기 - pyrasis

S3와 CloudFront에 CORS 세팅해주기

CORS 에러도 무조건 발생하죠.
다음 레퍼런스들을 참조해서 CORS 문제를 해결하였습니다.

레퍼런스 : S3에 CORS 적용하기

레퍼런스 : CloudFront 에 CORS 적용하기

S3로 정적호스팅을 하니까요. 라우터가 동작하질 않아요..

저희 프로젝트 라우터 구조는 다음과 같았어요.

<domainName>/game
<domainName>/signup 등의 라우터가 동작을 안하고

Untitled.png

위와 같이 나오는거에요.
http status 는 403 이고요. 즉 액세스 차단되었대요.

네트워크 탭을 보니까.
favicon.ico는 받아와요.
X-Cache: Error from cloudfront === 요청하는 객체가 없다! 라는 뜻이래요.

왜 객체가 없다는거죠? S3에 정적호스팅을 해뒀는데요? 아니죠.
없는게 맞죠. s3 버킷에 /game이라는 디렉토리가 없을뿐더러 있더라도 우리가 원하는 동작은 react앱에서 라우터 타라는 거잖아요.

다음과 같이 검색했어요.
react app s3 cloudfront 403

참조한 내용을 긁어왔어요.

S3를 이용한 웹사이트 호스팅에서 index_document = "index.html"로 설정되어 있으므로 demo.example.com에 접속하면 자동으로 index.html을 보여주지만, 그 외의 파일을 경로에 맞게 보여준다. 즉 demo.example.com/app.js를 요청하면 S3 버킷에 app.js가 있어야 한다.

SPA는 최초 요청에서 index.html을 받아서 앱을 초기화한 뒤 라우팅을 하므로 페이지를 이동하면 각 페이지의 URL이 /newest/1이나 /show/5 같은 식이 될 수 있다. 처음 로딩하고 웹을 사용할 때는 잘 동작하지만, URL을 복사해서 demo.example.com/show/5로 접속한다면 S3 버킷에 show/5라는 파일이 없으므로 403 오류를 받게 된다. 원래대로라면 404여야 하지만 S3를 쓰고 있어서 403이 반환된다.

이렇게 설명이 되어있어요.

여기서 인사이트를 얻었습니다.

저희는 S3를 이용한 웹 사이트 호스팅에서
대문을 index.html로 정했어요.

image.png

그러면 엔드포인트를 통해 웹 서버에 요청을 보내면 웹 서버가 index.html를 반환해줘요.

브라우저는 반환받은 index.html를 렌더링해줘요.

그 외의 파일을 경로에 맞게 보여줘요. 즉 <domainName>/game 를 요청하면 S3 버킷에 game.js가 있어야 해요.

처음 로딩하고 웹을 사용할 때는 잘 동작하지만, URL을 복사해서 <domainName>/game로 접속한다면 S3 버킷에 game라는 파일이 없으므로 403 오류를 받게 되요.

원래대로라면 404여야 하지만 S3를 쓰고 있어서 403이 반환되요.

우리는 여기서 어떻게 할 것인가?

CloudFront에서는요 오리진에서 http 4xx or 5xx응답때 특정한 페이지를 설정 할 수 있게 되어있어요.

그래서 저희는 index.html를 정해줬어요.

image.png

image.png

이렇게 된다면

domainName/game 이란 요청이 403 반환하고

index.html로 가죠? 리액트 앱이니까요.

app.js에서 router를 타겠죠? 그리고 해당 router가 렌더링이 될것입니다.

이렇게 문제를 해결했습니다.

이렇게 문제를 해결하는데에 가장 베이스는 개념도였습니다. 개념도를 통해 문제가 생기는 곳을 의심하고 구글링하면서 문제를 구체화해서 해결하게 되었습니다.

image.png

레퍼런스: pushState를 사용하는 SPA를 S3와 CloudFront로 서비스하기 - Outsider's Dev Story

사용자 인증 방법

저희는 사용자 인증이 필요했어요. 즉 로그인 되지 않은 사람은 게임을 못하게 막고 싶었거든요.
구체적으로는 게임이 끝나고 게임결과를 post요청해도 server에서 받지 않게끔요.

SPA구조에서는 React의 생명주기 메서드인 ComponentDidMount라는 메서드가 있습니다. 이때 인증에 관련된 요청을 할 수도 있겠네요.
화면에 로그인한 회원이 누군지 보여주려면요.

쿠키-세션의 방식으로 사용자를 인증하였습니다.
image.png

아쉬운 점은 다음과 같이 Token을 이용하고 싶었어요.
hash로 토큰 내용을 감출 뿐 아니라, 세션도 필요 없고요.
DB I/O가 필요하지만 성능에 큰 영향을 주지는 않는다고 생각합니다.

Access Token + Refresh Token 인증 과정
image.png

레퍼런스 : 쉽게 알아보는 서버 인증 1편(세션/쿠키 , JWT) - 자유로운 오랑우탄

레퍼런스 : [JWT] 토큰(Token) 기반 인증에 대한 소개 - Velopert

마지막으로 아쉬운 점 개선해야할 점

  • 파일 무효화 자동화
  • 사용자 인증

파일 무효화 자동화

10일이라는 기간 내에 결과물을 보여줘야 하는 프로젝트였기 때문에 부족한 부분들이 많았지만, 꼭 가져가야 하는 부분이 웹캠을 통한 페이스 인식, 특히 눈 코 입을 인식하는 모듈을 가진 클라이언트 앱을 배포하는 것이었습니다.

그래서 미처 개발이 다 되지 않은 상태에서 배포를 했습니다. 그래서 기능이 추가될때마다 S3에 배포한 앱을 올리는데요. CDN에 캐시된 파일이 만료되기 전까지는 저희가 개발한 앱을 보여주지 않더라고요.

그래서 찾아낸 방법중 하나가 파일 무효화였습니다.
이 기능의 역할은 CloudFront 엣지 캐시에 파일이 만료되기 전에 파일을 제거합니다.
그리고 사용자가 파일을 요청하면 Cloudfront 최신 버전의 파일을 가져오도록 오리진에 반환합니다.
즉, 새로 배포한 앱을 캐시 서버에 저장한다는 얘기겠죠.

다만 이것을 자동화하는 방법을 몰랐습니다.
특이한 구조이긴 한데요. 배포후 바로 invalidation을 이어서 하는 방법을 찾지 못하고 지나간 케이스입니다.

사용자 인증

로그인성공후 서버에서 사용자 이름을 쿠키에 넣습니다.
그래서 페이지 이동시 쿠키에 사용자 이름이 있는지를 판단해서
로그인한 사용자만 페이지 이동하게끔 하였습니다.
반나절이라는 짧은 시간동안 사용자 인증 효과까지 보여주고 싶어서
간단하게 쿠키로 사용하였지만, 시간이 좀더 있었더라면,
JWT를 사용해서 했었으면 하는 아쉬움이 있네요.

레퍼런스 : 파일 무효화 - AWS cloudfront

레퍼런스 : Invalidation으로 CloudFront 콘텐츠 갱신하기 - pyrasis.com

profile
서로 아는 것들을 공유해요~

1개의 댓글