핵심 기능인 아이 돌보기 / 돌봄 맡기기 데이터의 CRUD API 를 구현하였으니, 다음으로는 로그인을 구현하기로 했다.
로그인을 빠르게 구현한 이유는 각 유저마다 '아이 돌보기' 데이터를 하나만 생성할 수 있도록 세운 정책에 의해 '돌보기' 데이터의 생성 API 는 현재 로그인된 유저의 정보를 가져오도록 코드를 작성하여 로그인이 필요했기 때문이다.
구현을 시작하기 전에는 전에 플러터로 개발한 앱에서 Firebase로 로그인을 해봤으니 금방 구현할 수 있겠다고 생각했었으나, 막상 해보니 내가 03
있었던 점이 있었음을 깨달아서 난관에 부딪혔다.
클라이언트 Firebase vs 서버 Firebase
내가 착각하고 있었던 점은 플러터 + Firebase 로그인을 할 때는 플러터 앱(클라이언트) 에서 Firebase (인증 서버) 로 직접 요청을 보내 인증을 받는다는 점이었다.
이때 플러터에서는 "클라이언트용" 파이어베이스 라이브러리가 필요했다
플러터에서는 ID, PW 정보를 담아 클라이언트 파이어베이스 라이브러리의 signIn() 메서드를 호출해 로그인을 진행하였다.
로그인에 성공하면 유저 정보가 담긴 데이터를 받아, 직접 유저 정보에 접근하여 읽고 쓸 수 있었다.
그러나 지금 하고 있는 프로젝트에서는 '장고 + Firebase' 조합으로서, 장고 앱(서버) 에서 Firebase (인증 서버) 로 요청을 보내 인증을 받은 뒤, 인증 정보를 프론트엔드(클라이언트)에 전달해주는 방식으로 구현해야 한다.
사실 프론트에서 Firebase Auth 에 직접 인증을 요청한뒤, 받은 유저 정보로 백엔드에서 데이트 조회를 요청해도 된다.
그러나 이렇게하면 백엔드의 로그인 역할을 프론트에 떠넘기는 느낌도 있고, 이번 기회에 백엔드용 Firebase 도 사용해보고 싶어서 이런 구조로 설계해보았다.
그러나 이렇게 구현하는데 한가지 발생한 문제
Firebase SDK 에서는 ID Token 을 받아서 디코딩하는 기능은 있어도, 이메일/패스워드를 받아서 직접 인증을 요청한 뒤 ID Token을 받는, 일종의 클라이언트 파이어베이스 라이브러리가 없었다.
그래서 이메일/패스워드 정보를 Firebase Auth에 넘길 때는, 장고 서버가 클라이언트가 되어서 클라이언트로서 요청을 보내야했다.
그런데 또 한가지 문제점은, 파이어베이스 클라이언트 라이브러리에서 공식적으로 파이썬을 지원하지 않는 다는 것이었다.
다행히 검색을 통해 pyrebase 라는 것을 알게되었는데, 이 레포지토리의 최종 업데이트는 5~6년전...
다시 검색해보니 pyrebase 를 계승한, pyrebase4 를 이용해서 로그인 문제를 해결할 수 있었다.
(pyrebase4의 최종 업데이트는 몇달 전이었다.)
로그아웃 구현하기
그런데 pyrebase4 를 이용해서 로그인을 구현하던 중 문제가 발생했다.
pyrebase4는 firebase 클라이언트 라이브러리의 파이썬 버전인데, 분명 공식적으로는 이메일/패스워드 로그인 / 회원가입 / 로그아웃 메서드를 지원하고 있다.
그러나 pyrebase4에서는 로그인과 회원가입은 지원하고 있지만, 로그아웃을 지원하고 있지 않았다.
처음에는 내가 못 찾는건 줄 알았다.
그러나 이렇게 공식 이슈 제기를 해도, 2년 반 동안 답이 없었다.
궁금해서 pyrebase4 말고 내가 직접 구현해야겠다는 마인드로 어떻게 구현했는지 코드를 뜯어봤다.
근데 일단 기여자 수에서 살짝 불안했다.
이렇게 인기가 많은 라이브러리의 기여자가 겨우 13명이라면, 기여는 정해진 권한이 있는 사람만 할 수 있는게 아닐까
그리고 구현체의 결과는 아래와 같았다.
구현 방식은 알고보니 관련 기능을 수행하는 구글 api 에 직접 요청을 보내는 것이었다.
이걸 보고나서 로그아웃을 직접구현해야겠다는 의지가 꺾이고 말았다.
찾아봐도 api 명세도 잘 안나오고 url 주소도, 요청방식도, request body 형식도 모르는데 어떻게 요청을 보내야할지도 몰랐다.
인터넷에서는 그냥 currentUser = None 으로 설정하면 된다고 하는데, 이건 누가봐도 안전한 방식은 아니었다.
왜냐하면 로그인은 id_token 방식을 이용하는데, 이렇게 currentUser = None 으로 하면, 당장 서버가 인식하기에 로그인된 유저가 없는 것으로 볼 수도 있겠지만, id_token은 여전히 유효하니 만약 id_token이 노출된다면 누군가 해당 유저가 로그인한 것과 같은 요청을 충분히 보낼 수 있었다.
그래서 다른 대안을 곰곰히 생각해봤는데, Firebase SDK Auth 공식 문서를 읽다가 좋은 방법을 발견했다.
(이것도 야매이긴 하지만..)
바로 revoke_refresh_tokens 를 이용하는 방법이다.
이 메서드는 원래 기존 id_token 으로 로그인한 유저가 기기를 분실하였거나 했을 때, 현재 사용가능한 id_token을 revoke 상태로 바꿔 일종의 분실된 id_token 상태로 만드는 것이다.
이 경우, id_token의 유효성을 체크할 때 아래와 같은 방식으로 체크하면 유저가 보낸 id_token이 유효한지 확인할 수 있다.
이렇게 revoked 여부도 같이 체크를 해서 revoked 된 상태라면 해당 id_token 역시 사용하지 못하는 id_token 으로 인식하게 만들었다.
이 로직은 로그인, 회원가입을 제외한 모든 API에 공통으로 적용되기 때문에 한번 로그아웃해서 그 id_token 이 revoked 되면 다시는 그 id_token으로 데이터를 받을 수 없게 된다. (즉, 새로 로그인을 해야한다.)
그래서 결과적으로는 '로그아웃 된 상태' 와 동일한 상태를 구현할 수 있게 되었다.
이 로직을 배포한 뒤, 프론트와 연동하는데에도 시간이 많이 걸렸다.
다들 개발이 익숙하지 않아서, 공부해가며 개발을 하고 있는데, http 에 헤더 값까지 직접 설정해서 요청보내는 것은 익숙하지 않았다.
그래도 다들 각자 파트를 열심히 공부해서 연동에는 성공할 수 있었다.
서버에 배포하고 프론트와 로그인 연동 테스트하기
위에서는 로그인 이후 로그인 된 상태의 유저의 요청을 연동하는 부분에서 힘들었던 점을 적었다면,
이 내용은 그 전에 로그인을 연동하는 부분에 대한 내용이다.
1월 21일이 중간 발표인데, 우리의 목표는 로그인 이후 유저의 데이터를 보여주는 것까지가 중간 발표의 구현 목표였다.
그런데 1월 20일까지도 로그인 기능 연동이 안돼서 21일 새벽 3시까지 연동을 테스트하였다.
이 과정에서 많은 부분을 생각하게 되었다.
우선 첫번째는 예외 케이스 처리의 중요성
당연히 에러 처리는 중요했지만, 일단 '로그인이 되게 한다' 라는 목표에 집중해서 구현한 나머지, 로그인에 실패한 상황 (이메일이 없는 이메일, 잘못된 비밀번호 등) 에 대한 에러 처리를 모두 서버에러 (500) 으로 일단 달아놨는데, 프론트랑 연동하는 과정에서 계속 500 에러가 나와서 에러 원인을 찾기가 힘들었다.
그래서 급하게 서버 배포한 버전에 print() 함수를 여기저기 붙인 뒤, 서버에 실시간으로 찍히는 에러 로그를 보면서 프론트와 합을 맞췄다.
만약 처음부터 에러처리를 제대로 다 구현해서 잘못된 요청에는 400 에러를 주고, 구체적인 에러메세지를 응답해주었다면 이런 고생은 할 필요가 없었을 것이다.
두번째는 로깅의 중요성
위에서 문제를 찾기 위해 열심히 print 문을 찍었다고 했는데, 사실 모든 except 문에 대해 로그를 남기는 코드를 적었더라면 그냥 로그파일만 쓱 읽으면서 에러를 충분히 처리할 수도 있었을 것 같다.
이걸 경험하면서 왜 log4j 같은 로깅 라이브러리가 필요한지 느낄 수 있었다.
그리고 이렇게 사이드로 프로젝트를 하다보니, 어차피 테스트를 골든웨이로 밖에 거의 해보질 않다보니 이런 예외처리에 대한 중요성을 계속 망각하게 되는 것 같다.
귀찮음이 느껴지는 것도 사실이고..
그런 점에서는 애초부터 골든 웨이 이외의 시도를 테스트로 다 만들어두고 해당 테스트를 통과하는 코드를 작성하는 TDD를 하는 편이 더 나았을까 하는 생각이 들었다.
로그인을 성공적으로 구현한 뒤, 그 다음부터는 다시 새로이 역할을 나눠 나는 실시간 채팅을 구현하기로 하였다.
이것도 공부하면서 여러가지를 느끼고 있는데, 이에 대해서 다음 글에 정리해보려고 한다.
'팀 프로젝트 > [2024] GDSC 프로젝트 트랙' 카테고리의 다른 글
[GDSC 프로젝트 트랙] 6. 게시글 CRUD, 댓글 CRUD API 구현 (0) | 2024.02.16 |
---|---|
[GDSC 프로젝트 트랙] 5. 장고(Django) 배포 & Github Action 이용한 CI/CD 구축 (0) | 2024.02.06 |
[GDSC 프로젝트 트랙] 4. 채팅 구현 방식 결정 & API 작성 (0) | 2024.02.01 |
[GDSC 프로젝트 트랙] 2. 맡기 CRUD API 구현 & 배포 (0) | 2024.01.19 |
[GDSC 프로젝트 트랙] 1. 팀 빌딩 & 아이디어 수집 & 화면 설계 (3) | 2024.01.07 |