如何实现无感刷新 Token

使用 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)机制通过在后台自动刷新访问令牌,确保用户在使用应用时不会因令牌过期而被迫重新登录,从而提高用户体验。实现无感刷新需要前后端协同工作,前端负责检测和刷新令牌,后端提供刷新令牌的接口。通过合理的设计和实现,可以有效地提高应用的安全性和用户体验。

posted @ 2024-07-30 19:00  最小生成树  阅读(819)  评论(0)    收藏  举报