iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
Vibe Coding

做一個團購後端,順便搞懂那些事系列 第 8

Day 8|登出只刪 refresh,那張還活著的 access 怎麼作廢?

  • 分享至 

  • xImage
  •  

JWT 的優點是 stateless——伺服器只驗簽章,不查資料庫就能確認身分,但是如果只有單支 token 會遇到一個無解的取捨:效期短,使用者每隔幾分鐘就被踢出去;效期長,登出後撤銷不掉。stateless是一把雙面刃,伺服器沒有記錄,就沒有辦法讓一張已簽發的 token 提前失效。

兩個需求無法用同一支 token 同時滿足,因此拆成access token和refresh token,各自負責一件事。

Access Token、Refresh Token 各自負責什麼?

Access Token 是效期 15 分鐘、以 RS256 簽章的 JWT,負責呼叫 API;token 本身不帶狀態,只靠簽章就能確認是誰簽發、是否過期,就算外洩,攻擊窗口也只有 15 分鐘。

Refresh Token 則是效期 7 天的 UUID,只負責換發新的 Access Token;它長效但有狀態,存在 Redis 裡並設定 7 天的存活時間,到期由 Redis 自動刪除;每次使用都必須查詢,也因此可以隨時手動刪除——由於只在換發時使用、頻率低,查一次儲存的成本可以接受。換句話說,把「高頻但短命」與「低頻但長效」分開,各自取用最適合的機制。

換發時要查 Redis,查不到就失效

public RefreshResponse refresh(RefreshRequest request) {
    // 1. Find refresh token in Redis
    String userId = tokenRedisService.getRefreshTokenUserId(request.getRefreshToken());
    if (userId == null) {
        throw new AuthenticationException("Refresh Token 已失效");
    }

    // 2. Find user
    User user = userMapper.selectById(userId);
    if (user == null) {
        throw new AuthenticationException("Refresh Token 已失效");
    }

    // 3. Generate new access token
    String accessToken = jwtUtils.generateAccessToken(user.getUserId());

    return new RefreshResponse(accessToken, accessTokenExpiresIn);
}

換發的第一步是拿 refresh token 去 Redis 查使用者,查不到就回「已失效」。所以要讓一顆 refresh token 作廢,只要刪掉 Redis 裡那筆資料就好,登出時做的正是這件事。

登出之後 Access Token 怎麼撤銷?

刪掉 Refresh Token 只能阻止換發,已經發出去的 Access Token 在剩餘效期內仍然有效,因此把它的 jti 放進黑名單,並且讓黑名單的 TTL 等於 token 的剩餘壽命

public void logout(String refreshTokenId, String accessToken) {
    // Blacklist access token in Redis
    if (accessToken != null) {
        try {
            Claims claims = jwtUtils.parseToken(accessToken);
            String jti = claims.getId();
            if (jti != null) {
                long remaining = jwtUtils.getRemainingSeconds(claims);
                tokenRedisService.blacklistAccessToken(jti, remaining);
            }
        } catch (Exception e) {
            // Access token may already be expired — ignore
        }
    }

    // Delete refresh token from Redis
    if (refreshTokenId != null) {
        tokenRedisService.deleteRefreshToken(refreshTokenId);
    }
}

Token 本身仍然不帶狀態,變的是驗證流程:驗完簽章與效期之後,還要再查一次 Redis 黑名單。每次請求因此多了一次 I/O,驗證不再是純 stateless,只能在stateless跟「登出能立即生效」取捨。


上一篇
Day 7|HS256 v.s RS256:兩種簽章演算法有什麼不同?
系列文
做一個團購後端,順便搞懂那些事8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言