우리 서비스에서는 JWT를 사용해 로그인을 구현했었다. 이에 따라 로그아웃도 기록하려고 한다.
사용자가 로그아웃을 진행하면 기존의 토큰들을 무효화 시켜주어야 한다. 그렇지 않으면 개발을 좀 아는 사람이 토큰 값을 network 탭에서 까보고 가지고 있다가 억지로 악의적인 요청을 보낼 수도 있다.
백엔드에서 이를 검증해주고 막아야 한다.
사용자에게 발급되는 토큰 중 하나인 at는 모든 요청에 대해 사용되기 때문에 반드시 만료해주어야 한다.
우리 서비스에서는 response body에 at를 담아서 보내주었기 때문에 프론트엔드에서 해당 토큰에 대한 접근이 가능했다. 따라서 로그아웃 요청이 이루어지면 프론트엔드에서 해당 토큰을 강제 만료시키는 방식으로 무효화를 진행했다.
리프레시 토큰도 사용자가 가지고 있다면 reissue 요청을 통해 access token을 새로 발급받을 수 있기 때문에 로그아웃 시 반드시 무효화 시켜주어야 한다.
우리 서비스에서는 refresh token을 쿠키에 담아서 관리하고 전송하고 있었기 때문에 at와는 달리 프론트엔드에서 접근이 불가능 했다. (httpOnly 설정을 사용하고 있었기 때문이다)
따라서 로그아웃 요청이 오면 해당 rt를 무효화 하는 작업을 백엔드에서 담당해서 진행했다.
@Transactional
public void logout(Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new BusinessExceptionHandler("Member not found", ErrorCode.NOT_FOUND_ERROR));
// 기존 리프레시 토큰을 블랙리스트에 추가
String key = "blacklist:" + member.getRefreshToken();
redisTemplateRT.opsForValue().set(key, "blacklisted", tokenProvider.getRefreshTokenExpiration(member.getRefreshToken()), TimeUnit.MILLISECONDS);
member.updateRefreshToken(null);
}
로그아웃 시 사용되는 로직은 위와 같다.
이렇게 리프레시 토큰을 redis에 저장하고, 해당 refresh token이 사용되는 로직에서는 redis에 해당 토큰이 있는지 찾아본 뒤 없다면 진행하도록 구성했다.
redis에 리프레시 토큰을 저장하면서 액세스 토큰에 대해서도 다시 생각해보게 되었다.
이전 포스트에서 언급했듯이, at 또한 쿠키로 전송한 뒤 만료시킬때 redis 에 저장했다면 좀 더 깔끔한 로직이 될 수 있을 것 같다.