使用 JSON Web Token (JWT) 進行身份驗證是一種常見的做法。然而,JWT 通常有一個有效期,當使用者的令牌過期時,如果不進行處理,使用者將被迫重新登入,這會影響使用者體驗。為了解決這個問題,可以實現無感重新整理(silent refresh)機制,自動重新整理令牌而不打擾使用者。
本文將介紹如何實現無感重新整理 Token 的技術方案,包括以下步驟:
- 生成和使用訪問令牌和重新整理令牌
- 自動重新整理訪問令牌
- 處理令牌重新整理失敗的情況
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)機制透過在後臺自動重新整理訪問令牌,確保使用者在使用應用時不會因令牌過期而被迫重新登入,從而提高使用者體驗。實現無感重新整理需要前後端協同工作,前端負責檢測和重新整理令牌,後端提供重新整理令牌的介面。透過合理的設計和實現,可以有效地提高應用的安全性和使用者體驗。