글을 읽는 대상: 🤔 JWT를 발급하는 로그인 상태 유지 방법으로 Access Token, Refresh Token을 사용하는 데 어디에 저장하면 좋을지 고민하는 독자
배경
팀 프로젝트를 하면서 로그인 기능을 구현하였다. 로그인 기능을 구현하면서 어떤 고민과정을 거쳤는지 정리해서 글을 적어보겠다.
목적
로그인 상태 유지를 위해 다음과 같은 방식을 사용할 수 있다.
Session 방식의 장단점
- 장점
- 정보 자체는 서버에 저장되어 있어 중요한 정보를 클라이언트에게 노출되지 않도록 할 수 있다.
- 단점
- 해커가 이를 탈취하여 클라이언트인척 위장할 수 있다.
- 요청이 많아지면 서버에 부하가 심해진다.
JWT 방식의 장단점
- 장점
- 데이터 위변조를 막을 수 있다.(JWT Signature 필드를 통해 위변조 여부 확인 가능)
- 인증 정보에 대한 별도의 저장소 필요 없음
- 단점
- 토큰을 탈취당하면 대처가 어렵다. → 유효기간 만료될 때까지 계속 사용
- Payload 자체는 암호화가 되지 않기 때문에 유저의 중요한 정보는 담을 수 없다.(하지만 JWT 자체는 암호화 된다.)
Session id로 발급하는 방법은 세션 정보를 서버 내부나 DB에 저장하는데 매 요청마다 접근하면 서버 부하가 높아진다는 단점이 있다. 그래서 필자는 프로젝트에서 Session을 사용하지 않고 JWT 방식을 사용하였다.
그러면 JWT를 어떻게 사용하는지 알아보자
JWT를 활용하여 Access Token과 Refresh Token을 발급받는 방식으로 로그인을 구현하였다.
Access Token : 리소스에 접근하기 위해 사용되는 토큰
Refresh Token : Access Token이 만료되었을 때 갱신하기 위한 토큰
발급받은 토큰을 클라이언트에서 가지고 있는 방법으로는 Cookie에 저장하는 방법과 in-memory에 저장하는 방법이 있다. 필자는 Access Token은 in-memory에 저장, Refresh Token은 Cookie에 저장하는 방식으로 구현하였다. 왜 이런 방식으로 저장했는지 아래에서 추가적으로 설명하겠다.
Access Token을 in-memory에 저장한 이유는 다음과 같다.
하지만 장점만 존재한 것이 아니라 단점도 존재한다. 새로고침 시 Access Token이 없어져 다시 받아와야 한다. 이 때 사용자 경험(UX)을 고려하여 클라이언트에서 Refresh Token을 활용한 Silent Token Refresh 방식으로 받아온다.
Access Token을 in-memory에 저장함으로 새로고침 시 Token이 없어진다. 이 때 Refresh Token도 없어지면 결국 다시 로그인을 해야하는 불편함이 발생한다. 그래서 Refresh Token은 새로고침에도 지워지지 않도록 Cookie에 저장했다. Cookie로 저장하되 Cookie 옵션을 줘서 보안을 강화했다. 사용한 옵션은 다음과 같다.
/api/user/auth/refresh 에서만 접근 가능하도록 하였다.하지만 Cookie에 저장되면 탈취될 수 있다는 문제점이 있다. 이럴 경우에 대처할 방법이…😱😱 생각만해도 끔찍하다
탈취되는 경우를 고려하여 로그인 사용자마다 한 개의 Refresh Token을 매핑(로그인 시 Refresh Token을 DB에 저장)하고 Refresh Token이 유효한지 검증하는 로직을 추가하였다.
🧐 고민 사항
Refresh Token을 DB에 저장하여 접근하면, Session을 사용하는 것과 비슷하게 서버에서 DB에 접근하니 부하가 발생하지 않을까?아니다. Session은 요청이 올 때마다 DB에 접근하는 반면 Refresh Token은 재발급할때만 DB에 접근하기 때문에 접근 빈도가 적어서 Session 보다 부하가 덜 든다.
+) DB에 Refresh Token을 저장했는데 DB로는 NoSQL인 Redis에 저장하는 방식이 좋다고 생각한다. 이유는 빠른 접근 속도를 가진다는 장점과 데이터가 휘발되더라도 어플리케이션이 동작하는데 큰 문제가 발생하지 않다는 점이다.

[api 요청]
[유효하지 않은 Access Token]
[유효하지 않은 Refresh Token]
한계점으로, Refresh Token이 탈취당했는지 알 수 있으면 폐기하는 방법까지 추가하면 더 좋았을 텐데 위 방법은 그렇지 못하여 완전한 보안이라고 할 수는 없다.
공부하다보니 완전한 보안은 없는 것 같다는 느낌이 들었다.🤯 (어느정도 trade-off가 존재하였다.)