이전 Access Token을 계속 허용하지 않도록 검증한 방법
토큰 만료 시간을 확인하고 서명을 검증하는 것만으로는 “이 토큰보다 나중에 새 토큰이 발급됐다”는 사실을 알 수 없다. 당시 프로젝트에서는 한 세대 전 토큰뿐 아니라 더 오래된 Access Token으로도 새 토큰을 발급받을 수 있는 문제가 있었다. 요청에 담긴 토큰 자체는 아직 유효했지만, 사용자는 이미 다음 토큰으로 넘어간 상태였다.
당시 적용한 방식은 토큰 발급 시 Refresh Token과 가장 최근 Access Token을 캐시에 저장하고, 요청마다 전달된 Access Token이 Redis에 기록된 최신 토큰과 일치하는지 확인하는 것이었다. 비교 결과가 다르면 오래된 토큰으로 보고 요청을 거절했다. 무상태 검증에 상태 저장소를 더해 토큰 세대를 구분한 셈이다.
최신 Access Token 비교의 장점과 비용
이 검사는 폐기된 이전 Access Token을 빠르게 차단한다. 반면 모든 요청에서 Redis 조회가 추가되고, Access Token을 캐시에 보관·갱신·만료시키는 정책이 필요해진다. Redis 장애 시 인증을 실패시킬지, 제한적으로 허용할지도 서비스의 보안 요구에 따라 정해야 한다.
또한 “최신 토큰 문자열과 정확히 일치하는가”를 보는 것은 특정 프로젝트의 해결책이다. 토큰 원문을 캐시에 그대로 저장하는 방식은 저장소 유출 시 유효한 자격 증명을 더 노출할 수 있다. 상태를 보관해야 한다면 토큰 전체보다 jti, 세션 식별자, 토큰 해시 또는 사용자별 버전처럼 목적에 맞는 최소 식별자를 검토할 수 있다. 값은 권한 정보가 아니더라도 민감한 인증 메타데이터로 다뤄야 한다.
Refresh Token은 회전과 재사용 탐지가 핵심
Access Token 검증과 Refresh Token 발급은 별개의 경계다. OAuth 2.0 보안 모범 사례인 RFC 9700은 공개 클라이언트의 Refresh Token 재사용 탐지를 위해 발신자 제한 또는 토큰 회전 중 하나를 요구한다. 회전 방식에서는 갱신할 때 새 Refresh Token을 발급하고 이전 토큰을 무효화하며, 이전 토큰과 새 토큰의 관계를 서버가 기억해 재사용을 알아챈다.
따라서 단순히 “새 Access Token을 줬다”에서 끝내면 안 된다. 갱신 요청이 성공할 때 이전 Refresh Token을 원자적으로 소비하고, 새 토큰을 연결된 토큰 계열로 저장해야 한다. 무효화된 토큰이 다시 들어오면 재사용 가능성을 기록하고 해당 계열을 폐기하는 대응을 검토한다. 동시에 여러 탭이나 요청이 만료 직후 갱신을 시도하면 정상 클라이언트의 동시 요청도 재사용으로 보일 수 있으므로, 클라이언트 갱신을 한 번으로 묶거나 서버에서 경쟁을 원자적으로 처리해야 한다.
브라우저에서 토큰을 쿠키로 운반한다면 HttpOnly, Secure, 적절한 SameSite 설정과 CSRF 방어를 함께 설계한다. 반대로 클라이언트 저장소에 토큰을 둔다면 XSS가 토큰을 읽을 수 있는지부터 검토해야 한다. 어떤 저장 방식을 택하든 Access Token 만료만 짧게 줄이는 것으로 탈취 후 재사용을 완전히 막을 수는 없다.
검증 기준을 먼저 적어두기
현재 다시 설계한다면 다음 상태를 테스트로 고정하겠다.
- 서명과 만료 시간이 유효한 현재 Access Token은 허용한다.
- 로그아웃되거나 교체된 토큰은 정책에 따라 거절한다.
- Refresh Token은 한 번만 성공하고, 이미 사용한 토큰의 재사용은 탐지한다.
- 동시 갱신이 정상 사용자를 불필요하게 로그아웃시키지 않는지 확인한다.
- 캐시 장애와 토큰 저장소 만료 시 동작을 문서화한다.
Redis에 최신 Access Token을 비교했던 방식은 당시 문제를 해결하기 위한 실용적인 상태 검증이었다. 지금은 Refresh Token 회전과 재사용 탐지, 동시 갱신, 안전한 저장까지 하나의 수명주기로 바라보는 것이 더 중요하다고 정리한다. 로그인 흐름의 다른 실무 경험은 LDAP 인증에서 필수 속성 누락을 처리한 기록에서 이어진다.
참고 문서: RFC 9700 OAuth 2.0 Security Best Current Practice
당시 기록: 토큰 검증 오류