[NestJS | Redis] 세션 스토리지 만들기
본 게시물은 이전에 운영하던 velog에서 작성됨 (2023년 3월 12일 작성)
서버 scale-out 시 세션 불일치 문제가 발생하기 마련이다.
sticky 세션이나 클러스터 세션 등 여러 방법으로 해결할 수 있지만
세션 스토리지로 세션들을 관리하는 것이 가장 좋은 방법인 것 같다.
물론 모든 것에는 트레이드 오프가 있듯이 세션 스토리지 서버가 다운되면
인증 로직 자체가 작동할 수 없다는 단점도 있으나 나머지 둘 보다 낫다고 생각한다. (DB 레이어에서 고가용성을 위한 방법이 또 여러가지 있으니까)
아무튼 REDIS를 이용해서 세션 스토리지를 구성했다. REDIS는 이번에 처음 사용해보는데, 데이터가 key-value 구조고 이에 따라 명령어도 단순해서 굉장히 편하다는 느낌을 받았다.
😺 세션 스토리지 서버 만들기
https://redis.com/redis-enterprise-cloud/overview/
감사하게도 무료로 서버를 만들 수 있다. 메뉴얼대로 하면 뚝딱 만들 수 있다.
🐯 애플리케이션과 REDIS 연결
NEST DOCS에서는 cache-manager-redis-store라는 패키지를 이용한 REDIS 연결 방법을 제시한다. 그런데 redis v4 부터는 지원을 하지 않는다.(Redis cloud는 무려 v6)
그래서 이런 저런 패키지를 설치해서 실패를 거듭하다가 @liaoliaots/nestjs-redis라는 패키지를 알게 되었다.
npm i @liaoliaots/nestjs-redis
설치 뒤 인증을 처리할 auth 도메인을 생성한다
nest g module auth
nest g controller auth
nest g service auth
app.module에 RedisModule을 등록한다.
🐹 인증 과정
인증 과정은 다음과 같다.
- 프론트에게 아이디와 비밀번호 전달 받음
- 아이디와 비밀번호가 올바른지 확인
- 올바르면 세션 아이디로 사용될 uuid 발급
- uuid를 key 값으로 user PK와 nickname을 value로 REDIS에 저장
- 쿠키에 세션 아이디 담아서 프론트로 전달
🐻 인증 과정을 코드로
로그인 컨트롤러에서는 사용자의 아이디와 패스워드를 확인해서 세션 아이디를 발급하는 login 서비스와 세션 아이디와 유저 정보를 레디스에 저장하는 setSessionInfomation 서비스가 존재한다.
유저 데이터가 저장된 mysql DB에서 유저 정보가 있는지 확인하고 bcrypt로 비밀번호도 확인한다. 이 과정을 패스하면 uuid가 발급된다.
우선 발급받은 세션 아이디가 레디스에 저장되어 있는지 확인한다. 만약 레디스에 있다면 세션이 있음에도 잘못된 방법으로 로그인 시도를 하는 것이니 예외를 발생시킨다.
그다음 레디스에 유저 pk와 닉네임을 레디스에 해시 자료구조로 저장하고 만료일까지 지정해준다.
마지막으로 컨트롤러에서 쿠키에 세션 아이디를 담아주면 인증 과정은 끝이 난다.
🐺 인가 과정 및 코드
NEST에서는 GUARDS라는 개념이 존재하는데, 말 그대로 문지기 역할을 한다. 요청이 컨트롤러에 도달하기 전에 해당 요청이 API에 접근할 권한이 있는 지, 인증이 된 주체인지를 판단한다.
정확하게는 미들웨어 호출 이후 인터셉터와 파이프 호출 전에 실행된다.
우선 헤더에서 쿠키를 가져온다. 만약 쿠키가 비어있으면 로그인 되지 않은 사용자이므로 예외처리한다. (사실 이 로직은 수정할 부분이 있다. 세션 아이디 외에도 쿠키에 값이 있을 수 있기 때문에 먼저 세션 아이디를 추출한 뒤에 세션 아이디의 유무로 인증 상태를 파악해야한다. 그러나 현재 쿠키에 담기는 값이 세션 아이디 외에는 없기 때문에 틀린 로직은 아니다)
이후 세션 아이디를 이용해 redis에서 유저 데이터를 뽑아온다. 만약 redis에 세션 아이디가 존재하지 않으면 만료일이 다 된 세션이라고 판단하고 예외를 발생시킨다.
예외를 모두 통과하면 true를 반환하여 인터셉터나 파이프, 컨트롤러로 넘어가게 된다. false나 예외를 던지면 다음 단계로 접근할 수 없다.
🐣 정리 끝