如何實現無感重新整理 Token

最小生成树發表於2024-07-30

使用 JSON Web Token (JWT) 進行身份驗證是一種常見的做法。然而,JWT 通常有一個有效期,當使用者的令牌過期時,如果不進行處理,使用者將被迫重新登入,這會影響使用者體驗。為了解決這個問題,可以實現無感重新整理(silent refresh)機制,自動重新整理令牌而不打擾使用者。

本文將介紹如何實現無感重新整理 Token 的技術方案,包括以下步驟:

  1. 生成和使用訪問令牌和重新整理令牌
  2. 自動重新整理訪問令牌
  3. 處理令牌重新整理失敗的情況

1. 生成和使用訪問令牌和重新整理令牌

生成訪問令牌和重新整理令牌

在使用者登入時,伺服器生成兩個令牌:訪問令牌(access token)和重新整理令牌(refresh token)。訪問令牌用於驗證使用者身份,有較短的有效期;重新整理令牌用於獲取新的訪問令牌,有較長的有效期。

import jwt
import datetime

def generate_tokens(user_id):
    access_token = jwt.encode({
        'user_id': user_id,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
    }, 'access_secret_key', algorithm='HS256')

    refresh_token = jwt.encode({
        'user_id': user_id,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(days=7)
    }, 'refresh_secret_key', algorithm='HS256')

    return access_token, refresh_token

返回令牌給客戶端

在使用者登入成功後,將訪問令牌和重新整理令牌返回給客戶端,並將它們儲存在安全的位置。

// 示例:使用 axios 處理登入請求並儲存令牌
axios.post('https://yourapi.com/login', {
  username: 'user',
  password: 'password'
}).then(response => {
  localStorage.setItem('access_token', response.data.access_token);
  localStorage.setItem('refresh_token', response.data.refresh_token);
});

2. 自動重新整理訪問令牌

攔截請求並檢查令牌

在前端,使用 Axios 攔截器檢查每個請求的訪問令牌是否即將過期。如果即將過期,使用重新整理令牌獲取新的訪問令牌。

import axios from 'axios';
import jwt_decode from 'jwt-decode';

// 建立 Axios 例項
const api = axios.create({
  baseURL: 'https://yourapi.com',
});

// 新增請求攔截器
api.interceptors.request.use(async (config) => {
  let accessToken = localStorage.getItem('access_token');
  const refreshToken = localStorage.getItem('refresh_token');

  if (accessToken) {
    const { exp } = jwt_decode(accessToken);
    const expirationTime = exp * 1000;
    const currentTime = Date.now();

    // 如果訪問令牌即將過期,重新整理令牌
    if (expirationTime - currentTime < 5 * 60 * 1000) { // 5 分鐘
      try {
        const response = await axios.post('https://yourapi.com/refresh-token', { token: refreshToken });
        accessToken = response.data.access_token;
        localStorage.setItem('access_token', accessToken);
      } catch (error) {
        console.error('Unable to refresh token', error);
        // 重新整理令牌失敗,處理失敗邏輯
      }
    }

    config.headers['Authorization'] = `Bearer ${accessToken}`;
  }

  return config;
}, error => {
  return Promise.reject(error);
});

伺服器端重新整理令牌

在伺服器端,實現重新整理令牌的邏輯,驗證重新整理令牌並生成新的訪問令牌。

from flask import Flask, request, jsonify
import jwt
import datetime

app = Flask(__name__)

@app.route('/refresh-token', methods=['POST'])
def refresh_token():
    try:
        refresh_token = request.json.get('token')
        decoded_refresh_token = jwt.decode(refresh_token, 'refresh_secret_key', algorithms=['HS256'])
        user_id = decoded_refresh_token['user_id']

        new_access_token = jwt.encode({
            'user_id': user_id,
            'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
        }, 'access_secret_key', algorithm='HS256')

        return jsonify({'access_token': new_access_token})
    except jwt.ExpiredSignatureError:
        return jsonify({'error': 'Refresh token expired'}), 401
    except jwt.InvalidTokenError:
        return jsonify({'error': 'Invalid refresh token'}), 401

if __name__ == '__main__':
    app.run()

3. 處理令牌重新整理失敗的情況

在實際應用中,可能會遇到重新整理令牌失敗的情況,如重新整理令牌已過期或無效。此時需要處理這些情況,通常是引導使用者重新登入。

在前端處理重新整理失敗

在前端攔截響應錯誤,當重新整理令牌失敗時,清除儲存的令牌並重定向使用者到登入頁面。

// 新增響應攔截器
api.interceptors.response.use(response => {
  return response;
}, error => {
  if (error.response.status === 401 && error.config && !error.config.__isRetryRequest) {
    localStorage.removeItem('access_token');
    localStorage.removeItem('refresh_token');
    window.location.href = '/login';
  }
  return Promise.reject(error);
});

總結

無感重新整理(silent refresh)機制透過在後臺自動重新整理訪問令牌,確保使用者在使用應用時不會因令牌過期而被迫重新登入,從而提高使用者體驗。實現無感重新整理需要前後端協同工作,前端負責檢測和重新整理令牌,後端提供重新整理令牌的介面。透過合理的設計和實現,可以有效地提高應用的安全性和使用者體驗。

相關文章