JWT 设置24小时有效期及前端过期判断

Gary Chen
JWT 设置24小时有效期及前端过期判断

JWT 设置24小时有效期及前端过期判断

1. 后端设置JWT 24小时有效期

在后端生成JWT时,可以设置exp(expiration time)声明来指定令牌的过期时间(24小时后过期):

// Node.js 示例 (使用jsonwebtoken库)
const jwt = require('jsonwebtoken');
const secret = 'your-secret-key';

function generateToken(user) {
  return jwt.sign({
    userId: user.id,
    // 设置24小时后过期 (以秒为单位)
    exp: Math.floor(Date.now() / 1000) + (24 * 60 * 60),
    // 也可以使用库的默认选项
    // 例如: expiresIn: '24h'
  }, secret);
}

2. 前端判断JWT是否过期

前端可以通过以下方式判断JWT是否过期:

方法1:解码JWT检查exp字段

function isTokenExpired(token) {
  try {
    // 解码token (不需要验证签名)
    const decoded = JSON.parse(atob(token.split('.')[1]));
    
    // 检查过期时间 (exp是以秒为单位的时间戳)
    return decoded.exp * 1000 < Date.now();
  } catch (e) {
    // 如果token格式无效,视为已过期
    return true;
  }
}

// 使用示例
const token = localStorage.getItem('jwtToken');
if (isTokenExpired(token)) {
  // token已过期,执行重新登录等操作
  console.log('Token已过期');
  logout();
} else {
  // token仍有效
  console.log('Token有效');
}

方法2:使用 jwt-decode 库

安装库:

npm install jwt-decode
# 或
yarn add jwt-decode

使用示例:

import jwtDecode from 'jwt-decode';

function checkTokenExpiry() {
  const token = localStorage.getItem('jwtToken');
  
  if (!token) return true; // 没有token视为过期
  
  try {
    const { exp } = jwtDecode(token);
    return exp * 1000 < Date.now();
  } catch (e) {
    return true; // 解码失败视为过期
  }
}

// 使用示例
if (checkTokenExpiry()) {
  // 执行token刷新或重新登录
  refreshTokenOrLogin();
}

3. 完整的前端Token管理方案

class AuthService {
  constructor() {
    this.tokenKey = 'jwtToken';
    this.refreshTokenKey = 'refreshToken';
  }

  // 保存Token
  setAuthTokens({ token, refreshToken }) {
    localStorage.setItem(this.tokenKey, token);
    if (refreshToken) {
      localStorage.setItem(this.refreshTokenKey, refreshToken);
    }
  }

  // 获取Token
  getToken() {
    return localStorage.getItem(this.tokenKey);
  }

  // 检查Token是否有效
  isTokenValid() {
    const token = this.getToken();
    if (!token) return false;
    
    try {
      const { exp } = jwtDecode(token);
      return exp * 1000 > Date.now();
    } catch {
      return false;
    }
  }

  // 刷新Token
  async refreshToken() {
    const refreshToken = localStorage.getItem(this.refreshTokenKey);
    if (!refreshToken) throw new Error('No refresh token available');
    
    try {
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ refreshToken }),
      });
      
      if (!response.ok) throw new Error('Refresh failed');
      
      const { token, refreshToken: newRefreshToken } = await response.json();
      this.setAuthTokens({ token, refreshToken: newRefreshToken });
      return token;
    } catch (error) {
      this.clearTokens();
      throw error;
    }
  }

  // 清除Token
  clearTokens() {
    localStorage.removeItem(this.tokenKey);
    localStorage.removeItem(this.refreshTokenKey);
  }

  // 自动刷新Token的请求拦截器
  setupInterceptor(axiosInstance) {
    axiosInstance.interceptors.request.use(async (config) => {
      if (this.isTokenValid()) {
        config.headers.Authorization = `Bearer ${this.getToken()}`;
        return config;
      }
      
      try {
        const newToken = await this.refreshToken();
        config.headers.Authorization = `Bearer ${newToken}`;
        return config;
      } catch {
        // 刷新失败,跳转到登录页
        window.location.href = '/login';
        return Promise.reject(new Error('Session expired'));
      }
    });
  }
}

// 使用示例
const authService = new AuthService();
authService.setupInterceptor(axios); // 设置axios拦截器

4. 最佳实践建议

  1. 双重检查:即使前端检查token未过期,后端也必须验证,因为客户端时间可能被篡改
  2. 自动刷新:实现token自动刷新机制,在token快过期时自动获取新token
  3. 安全存储:考虑使用HttpOnly cookies存储token,增加安全性
  4. 过期处理:提供良好的用户体验,token过期时提示用户或自动刷新
  5. 时间同步:确保客户端和服务器时间同步,避免因时间差导致的判断错误

5. 定时检查Token过期

可以在应用初始化时设置定时器,定期检查token状态:

function setupTokenChecker(authService) {
  // 每分钟检查一次
  setInterval(() => {
    if (!authService.isTokenValid()) {
      console.log('Token已过期或即将过期');
      // 执行刷新或登出操作
    }
  }, 60 * 1000);
}