feat: 实现Token刷新机制,缩短登录有效期
安全改进: - Access Token有效期从7天缩短为2小时 - 添加Refresh Token机制(有效期7天) - 关闭浏览器后较快失效,提升安全性 后端修改(auth.js): - 添加generateRefreshToken函数生成刷新令牌 - 添加refreshAccessToken函数验证并刷新access token - 分离ACCESS_TOKEN_EXPIRES和REFRESH_TOKEN_EXPIRES配置 后端修改(server.js): - 登录时返回refreshToken和expiresIn - 添加/api/refresh-token接口用于刷新token - Cookie有效期同步调整为2小时 前端修改(app.js): - 保存refreshToken到localStorage - 添加自动刷新定时器(过期前5分钟刷新) - 页面加载时若token过期自动尝试刷新 - 登出时清除refreshToken和定时器 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
const { UserDB } = require('./database');
|
||||
|
||||
// JWT密钥(必须在环境变量中设置)
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production';
|
||||
// Refresh Token密钥(使用不同的密钥)
|
||||
const REFRESH_SECRET = process.env.REFRESH_SECRET || JWT_SECRET + '-refresh';
|
||||
|
||||
// Token有效期配置
|
||||
const ACCESS_TOKEN_EXPIRES = '2h'; // Access token 2小时
|
||||
const REFRESH_TOKEN_EXPIRES = '7d'; // Refresh token 7天
|
||||
|
||||
// 安全检查:验证JWT密钥配置
|
||||
const DEFAULT_SECRETS = [
|
||||
@@ -36,19 +43,77 @@ if (DEFAULT_SECRETS.includes(JWT_SECRET)) {
|
||||
|
||||
console.log('[安全] JWT密钥已配置');
|
||||
|
||||
// 生成JWT Token
|
||||
// 生成Access Token(短期)
|
||||
function generateToken(user) {
|
||||
return jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
is_admin: user.is_admin
|
||||
is_admin: user.is_admin,
|
||||
type: 'access'
|
||||
},
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
{ expiresIn: ACCESS_TOKEN_EXPIRES }
|
||||
);
|
||||
}
|
||||
|
||||
// 生成Refresh Token(长期)
|
||||
function generateRefreshToken(user) {
|
||||
return jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
type: 'refresh',
|
||||
// 添加随机标识,使每次生成的refresh token不同
|
||||
jti: crypto.randomBytes(16).toString('hex')
|
||||
},
|
||||
REFRESH_SECRET,
|
||||
{ expiresIn: REFRESH_TOKEN_EXPIRES }
|
||||
);
|
||||
}
|
||||
|
||||
// 验证Refresh Token并返回新的Access Token
|
||||
function refreshAccessToken(refreshToken) {
|
||||
try {
|
||||
const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
|
||||
|
||||
if (decoded.type !== 'refresh') {
|
||||
return { success: false, message: '无效的刷新令牌类型' };
|
||||
}
|
||||
|
||||
const user = UserDB.findById(decoded.id);
|
||||
|
||||
if (!user) {
|
||||
return { success: false, message: '用户不存在' };
|
||||
}
|
||||
|
||||
if (user.is_banned) {
|
||||
return { success: false, message: '账号已被封禁' };
|
||||
}
|
||||
|
||||
if (!user.is_active) {
|
||||
return { success: false, message: '账号未激活' };
|
||||
}
|
||||
|
||||
// 生成新的access token
|
||||
const newAccessToken = generateToken(user);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
token: newAccessToken,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
is_admin: user.is_admin
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
if (error.name === 'TokenExpiredError') {
|
||||
return { success: false, message: '刷新令牌已过期,请重新登录' };
|
||||
}
|
||||
return { success: false, message: '无效的刷新令牌' };
|
||||
}
|
||||
}
|
||||
|
||||
// 验证Token中间件
|
||||
function authMiddleware(req, res, next) {
|
||||
// 从请求头或HttpOnly Cookie获取token(不再接受URL参数以避免泄露)
|
||||
@@ -142,7 +207,11 @@ function isJwtSecretSecure() {
|
||||
module.exports = {
|
||||
JWT_SECRET,
|
||||
generateToken,
|
||||
generateRefreshToken,
|
||||
refreshAccessToken,
|
||||
authMiddleware,
|
||||
adminMiddleware,
|
||||
isJwtSecretSecure
|
||||
isJwtSecretSecure,
|
||||
ACCESS_TOKEN_EXPIRES,
|
||||
REFRESH_TOKEN_EXPIRES
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user