프로젝트를 진행하면서, 사용자의 인증 여부를 확인하기 위해 JWT 토큰 기반의 방식을 선택했다.
서버의 자원을 사용하지 않고 클라이언트에 보관하여 stateless 하다는 특징과 어떤 서버로 요청을 보내도 해당 토큰이 유효한지 확인하면 되는 확장성에 용이한 점을 이유로 들었다.
처음 사용하면서 access token 만으로 인증 방식을 진행했지만, access token을 탈취당했을 상황의 문제점에 대해 고려하면서 refresh token을 도입하여 보완하고, 로그아웃 이후의 탈취상황을 고려해 블랙리스트(Blacklist)라는 개념도 도입해봤다.
이때 클라이언트에 노출되어 비교적 탈취당하기 쉬운 access token의 특성상 유효기간을 짧게 30분 정도로 가져갔다. 이로인해, 30분 마다 만료되는 access token을 재발급 받기위해 사용자는 반복적으로 로그인을 해야하는 문제점이 생긴다. 또한, 단일 토큰으로 access token만 사용한다면, 해당 토큰이 탈취당했을 때의 위험도 크다.
이를 보완하기 위해 서버측에서 별도로 보관하기 위한 refresh token을 추가했다.
refresh token을 도입하여 access token의 유효기간을 짧게하여 보안도 높이면서 편의성도 챙길 수 있게 되었다.
이제 클라이언트에서 header에 담아 보낸 access token의 유효기간이 만료된 상황에는 어떻게 동작할까?
이처럼 만료된 access token으로 에러가 반환되고, refresh token을 다시 담아서 재발급을 요청하는 과정이 추가되어야 한다.
하지만, 위와 같은 클라이언트와 서버사이의 통신이 비효율적으로 생각되었다.
서버까지의 통신과정을 조금이라도 해소하기위해, 클라이언트에서 항상 요청을 보내기 전에 access token이 만료된 토큰인지 확인한다면 어떨까?
* 클라이언트에서 Access token 만료 확인방법
import jwt_decode from "jwt-decode";
const decode = jwt_decode(sessionStorage.getItem("access-token"));
두 가지 방법을 활용하여 클라이언트에서 요청을 보내기 전, access token의 만료여부를 확인하고 필요한 요청을 보내는 것이다. 토큰이 만료되었다면, 서버로 refresh token을 보내 새로운 access token을 발급받을 수 있는 요청을 바로 보낸다면, 기존에 access token의 만료 에러를 받고 재발급을 요청하는 1번의 과정을 생략할 수 있겠다고 생각했다.
유저가 서비스 사용을 마치고 로그아웃하면서 저장된 access token을 삭제하도록 했다. 하지만, access token의 유효기간은 여전히 남아있고 이를 누군가에게 탈취당한다면 어떻게 될까??
유저가 로그아웃하여 access token과 refresh token을 삭제하겠지만, 아직 유효기간이 남아있는 access token이 탈취당해 짧은 유효기간이지만 해당 시간동안 문제가 발생할 것이라 생각했다.
이를 위해 블랙리스트 개념을 도입하여, 유저가 로그아웃시 access token의 남은 유효기간만큼 Redis에 유효기간을 설정하여 블랙리스트로 등록해놓는다. 유효기간이 끝나면 자연스럽게 Redis에서 삭제시켜 메모리에 남아있지 않도록 해준다.
이렇게되면, 사용자가 서비스 사용을 끝냈지만, 아직 유효기간이 끝나지않은 토큰을 Redis의 블랙리스트에 저장하고, 모든 클라이언트 요청이 들어올 때 Redis의 블랙리스트를 조회하도록 한다.
블랙리스트에 존재하는 토큰으로 인증 시도가 들어오면, 사용이 끝난 유저의 토큰이므로 누군가 탈취하여 사용하려는 의도로 판단하고 요청을 거부하도록 한다.
보안과 관련된 내용은 항상 어렵게 느껴진다..
그래도 JWT 토큰을 사용하면서 다양하게 생각해본 시간이 되었고, 블랙리스트라는 개념도 도입해보면서 보안적으로 좀 더 안전하게 사용하기 위해 노력했다고 생각된다.