mirror of
https://github.com/ZeroCatDev/ClassworksKV.git
synced 2025-12-07 13:03:09 +00:00
107 lines
3.8 KiB
JavaScript
107 lines
3.8 KiB
JavaScript
/**
|
||
* 纯账户JWT认证中间件
|
||
*
|
||
* 支持新的refresh token系统,验证access token
|
||
* 如果access token即将过期,会在响应头中提供新的token
|
||
* 适用于只需要账户验证的接口
|
||
*/
|
||
|
||
import {generateAccessToken, validateAccountToken, verifyAccessToken} from "../utils/tokenManager.js";
|
||
import {verifyToken} from "../utils/jwt.js";
|
||
import {PrismaClient} from "@prisma/client";
|
||
import errors from "../utils/errors.js";
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
/**
|
||
* 新的JWT认证中间件(支持refresh token系统)
|
||
*/
|
||
export const jwtAuth = async (req, res, next) => {
|
||
try {
|
||
const authHeader = req.headers.authorization;
|
||
|
||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||
return next(errors.createError(401, "需要提供有效的JWT token"));
|
||
}
|
||
|
||
const token = authHeader.substring(7);
|
||
|
||
try {
|
||
// 尝试使用新的token验证系统
|
||
const decoded = verifyAccessToken(token);
|
||
|
||
// 验证账户并检查token版本
|
||
const account = await validateAccountToken(decoded);
|
||
|
||
// 将账户信息存储到res.locals
|
||
res.locals.account = account;
|
||
res.locals.tokenDecoded = decoded;
|
||
|
||
// 检查token是否即将过期(剩余时间少于5分钟)
|
||
const now = Math.floor(Date.now() / 1000);
|
||
const timeUntilExpiry = decoded.exp - now;
|
||
|
||
if (timeUntilExpiry < 300) { // 5分钟 = 300秒
|
||
// 生成新的access token
|
||
const newAccessToken = generateAccessToken(account);
|
||
res.set('X-New-Access-Token', newAccessToken);
|
||
res.set('X-Token-Refreshed', 'true');
|
||
}
|
||
|
||
next();
|
||
} catch (newTokenError) {
|
||
// 如果新token系统验证失败,尝试旧的验证方式(向后兼容)
|
||
try {
|
||
const decoded = verifyToken(token);
|
||
|
||
// 从数据库获取账户信息
|
||
const account = await prisma.account.findUnique({
|
||
where: {id: decoded.accountId},
|
||
});
|
||
|
||
if (!account) {
|
||
return next(errors.createError(401, "账户不存在"));
|
||
}
|
||
|
||
// 将账户信息存储到res.locals
|
||
res.locals.account = account;
|
||
res.locals.tokenDecoded = decoded;
|
||
res.locals.isLegacyToken = true; // 标记为旧版token
|
||
|
||
next();
|
||
} catch (legacyTokenError) {
|
||
// 两种验证方式都失败
|
||
if (newTokenError.name === 'JsonWebTokenError' || legacyTokenError.name === 'JsonWebTokenError') {
|
||
return next(errors.createError(401, "无效的JWT token"));
|
||
}
|
||
|
||
if (newTokenError.name === 'TokenExpiredError' || legacyTokenError.name === 'TokenExpiredError') {
|
||
// 统一的账户JWT过期返回
|
||
// message: JWT_EXPIRED(用于客户端稳定识别)
|
||
// code: AUTH_JWT_EXPIRED(业务错误码)
|
||
return next(errors.createError(401, "JWT_EXPIRED", null, "AUTH_JWT_EXPIRED"));
|
||
}
|
||
|
||
return next(errors.createError(401, "token验证失败"));
|
||
}
|
||
}
|
||
} catch (error) {
|
||
return next(errors.createError(500, "认证过程出错"));
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 可选的JWT认证中间件
|
||
* 如果提供了token则验证,没有提供则跳过
|
||
*/
|
||
export const optionalJwtAuth = async (req, res, next) => {
|
||
const authHeader = req.headers.authorization;
|
||
|
||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||
// 没有提供token,跳过认证
|
||
return next();
|
||
}
|
||
|
||
// 有token则进行验证
|
||
return jwtAuth(req, res, next);
|
||
}; |