고민
세션을 사용하는 프로젝트의 경우 세션을 지우는 것으로 로그아웃을 진행한다. 그러면 세션을 더 이상 체크 할 수 없기 때문에 로그아웃이 된 것처럼 보이기 때문이다. 하지만 토큰을 사용하는 현재 프로젝트의 경우에는 사용할 수 없다. 때문에 로그아웃을 하기 위해서 세션처럼 확인을 할 수 있게 해주는 무언가가 필요했다.
1. RefreshToken
세션처럼 사용 할 수 있는 무언가가 필요했고 그것을 RefreshToken으로 정했다. 해당 토큰으로 존재하면 로그아웃이 아니고 지워졌다면 로그아웃 상태로 판단을 했다. 모놀리식으로 진행한 프로젝트에서는 전체 서비스가 하나의 DB를 바라보고 있기 때문에 테이블을 하나 추가하면 접근하기 굉장히 쉬웠다.
로직
간단히 말하자면 로그인이 성공하면 onAuthenticationSuccessHandler를 타게 되고 RefreshToken을 DB에 저장한다음 사용자에게 전달할 토큰 중 RefreshToken을 포함해서 전달한다. 그리고 사용자는 앞으로 요청을 할때는 항상 헤더에 RefreshToken을 달아서 전달해야 로그아웃을 확인 할 수 있다. 이 내용은 프론트에서 RefreshToken을 항상 기입해서 전달해야하는 상황이 오게 된다.
단점
- DB의 사용량이 생기고 Security 내부에서 DB까지 들어가는 비효율적인 방식이다
- RefreshToken의 사용처 중 로그인 유지의 기능을 완전히 사용하지 않은채로 진행하기 때문에 기능이 부실하다
- 아무 내용이 없는 토큰이지만 헤더에 계속해서 추가되어 전달되야 한다는 것이 경험에 전혀 좋지 않다.
과정
- 로그인을 성공하면 RefreshToken을 DB에 저장한다. 이때 사용자의 ID또한 같이 저장한다.
- 사용자에게 AccessToken 과 RefreshToken을 모두 전달하고 앞으로 요청에서 두 개 모두 전달하도록 만든다
- 이후 인증이 필요한 요청을 하면 RefreshToken을 확인하여 존재하는지 확인하고 AccessToken을 확인한다.
- 만약 로그아웃의 요청에 경우 RefreshToken을 확인하고 DB에서 지운다.
- 이후 만약 사용자가 인증이 필요한 요청을 한 경우 로그아웃을 한 사용자라는 것을 알린다.
2. Redis의 블랙리스트
Redis를 간단하게 생각해보면 In-Memory를 사용하는 DB라고 생각하면 좋다 또한 만료시간을 지정할 수 있기 때문에 굉장히 좋은 결과를 내보낼수 있다. 블랙리스트를 도입한건 모놀리식에서 MSA로 전환되면서 반영한것이라 과정이 조금 다르다.
로직
Redis에서도 블랙리스트를 사용했다. 블랙리스트라는 정확한 기능이 아니라 Redis의 여러가지 기능 중 하나를 활용해서 사용 할 수있는 내용이였다. set을 통해 사용자의 accessToken의 내용과 토큰의 이름 그리고 만료시간을 설정해서 해당 토큰을 가지고 있는 사용자는 redis에서 먼저 확인해서 만약 걸린다면 인증이 필요한 어떤 요청도 진행하지 못하고 로그아웃의 상태를 메세지로 전달하는 것이다.
과정
- 로그인을 성공하면 더 이상 RefreshToken을 발급하지 않고 AccessToken만 발급한다
- 이후 로그아웃의 요청이 오면 ApiGateWay에서 해당 요청을 받아 Redis에 블랙리스트 방식으로 저장한다. 이때 만료는 AccessToken과 동일 혹은 더 길게 잡아야 이후의 요청을 제한 할 수 있다
- 기존의 인가가 필요한 요청이 오면 redis를 확인하기 때문에 redis에 사용자의 accessToken의 정보가 담겨있다면 로그아웃 한 회원으로 인지하고 돌려보낸다.
- 로그아웃은 이제 굳이 서비스를 타지 않고 API가 처리한다는 것을 알 수 있다.
결과
Redis가 빠르다는 것을 위에서 말했고 로그아웃의 경우 서비스까지 가지 않고 가장 앞의 ApiGateWay에서 처리하는 것을 볼 수 있었다. 그것에 대한 결과로 보면 정말 어마어마한 속도를 볼 수 있다.
도입 전
도입 후
이것만 보면 정확히 알기 쉽지 않지만 요청의 포트번호를 보면 차이를 알 수있다. 8080과 apigateway의 포트번호인 8083을 보면 차이를 알 수 있다.
또한 각 응답에 대한 Time 쪽을 보면 26ms 에서 5ms 이라는 엄청난 속도의 차이를 보여준다. 역시 레디스
느낀점
사실 로그아웃에 대한 내용은 정말 말이 많았던 과정 중 하나였다. 워낙 방법이 다양했고 다들 맞다고 생각하는 것이 모두 달랐다. 나는 처음에 모놀리식으로 구상을 했을때는 바로 Redis를 도입하는 것 보다 정말 퓨어하게 코딩하고 싶었다. 그로인해 굉장히 많은 엉성함을 보였다. Security에서 DB를 접근하고 모든 요청에서 Refresh 하나만을 확인하기 위해 DB를 접근하다가 apigateway에서 Redis를 접근해서 O(1)의 조회 속도를 이용해 빠른 결과를 보여주는 것을 확인 할 수 있었다.
이래서 NoSQL을 통해 기능을 개선하는 것이 얼마나 좋은 효과를 보인다는 것을 알게되었고 Redis을 다양하게 활용하여 로그아웃에서 사용하는 것 뿐만 아니라 데이터를 조회하는 행위 자체를 Redis에 의존해서 사용하면 더욱 많은 성능개선이 될 것 같다.
하지만 Refresh는 로그아웃보다 로그인 유지를 위해 사용되는 경우가 많다 하지만 이번 프로젝트에서는 로그인을 유지하기 위한 기능이 없기때문에 그것이 조금 아쉽다. 하지만 refreshToken을 다시 도입해서 로그인 유지를 어떻게 더 진행할지 고민하는 것도 굉장히 좋은 방향이라고 생각한다.
추가 로직(비밀번호 변경)
나의 서비스 중 비밀번호를 변경하는 과정이 있다. 대부분 비밀번호가 변경되면 로그아웃이 진행되는데 기존에 사용하던 AccessToken을 블랙리스트로 해서 해당 토큰을 더이상 사용할 수 없게 하기 위해 추가적으로 등록시키는 것이 필요했다.
'프로젝트 > 항해99 개인 프로젝트' 카테고리의 다른 글
🚢 재고 관리를 위한 동시성 제어 (Monolithic Architecture) (2) | 2024.04.27 |
---|---|
🚢 WishList가 장바구니? (Redis 의 Hash타입 사용) (0) | 2024.04.26 |
🚢 이메일 인증 코드, Session 에서 Redis로 (0) | 2024.04.26 |
🚢 JPA 의 AttributeConverter (1) | 2024.04.26 |
🚢 @Scheduled 을 이용한 배송 상태 자동 체크 (1) | 2024.04.21 |