164 lines
5.3 KiB
JavaScript
164 lines
5.3 KiB
JavaScript
|
|
const jwt = require("jsonwebtoken");
|
|||
|
|
const logger = require("../utils/logger");
|
|||
|
|
const { sendError } = require("../utils/response");
|
|||
|
|
const tokenStore = require("./useTokenStore");
|
|||
|
|
const {
|
|||
|
|
isTypeInWhitelist,
|
|||
|
|
isPathInWhitelist,
|
|||
|
|
} = require("../config/auth-whitelist");
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 智能鉴权中间件
|
|||
|
|
* 根据请求的 type 参数或路径判断是否需要进行 JWT 验证
|
|||
|
|
* 如果 type 在白名单中或路径在白名单中,则跳过 JWT 验证
|
|||
|
|
*/
|
|||
|
|
const smartAuth = (req, res, next) => {
|
|||
|
|
try {
|
|||
|
|
const requestPath = req.path;
|
|||
|
|
// 检查路径是否在白名单中
|
|||
|
|
if (isPathInWhitelist(requestPath)) {
|
|||
|
|
console.log(`路径 ${requestPath} 在白名单中,跳过鉴权`);
|
|||
|
|
return next();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从请求参数中获取 type(支持 body 和 query)
|
|||
|
|
const type = req.body?.type || req.query?.type;
|
|||
|
|
|
|||
|
|
// 检查 type 是否在白名单中
|
|||
|
|
if (type && isTypeInWhitelist(type)) {
|
|||
|
|
console.log(`请求类型 ${type} 在白名单中,跳过鉴权`);
|
|||
|
|
return next();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 不在白名单中,执行正常的 JWT 验证
|
|||
|
|
const authHeader = req.headers.authorization;
|
|||
|
|
|
|||
|
|
if (!authHeader) {
|
|||
|
|
logger.warn(`未授权访问: ${requestPath}, type: ${type}`);
|
|||
|
|
return sendError(res, "未提供认证令牌", "No token provided", 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 支持两种格式: "Bearer token" 或 "token"
|
|||
|
|
const token = authHeader.startsWith("Bearer ")
|
|||
|
|
? authHeader.substring(7)
|
|||
|
|
: authHeader;
|
|||
|
|
|
|||
|
|
if (!token) {
|
|||
|
|
return sendError(res, "令牌格式错误", "Invalid token format", 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const secret = process.env.JWT_SECRET || "your-secret-key";
|
|||
|
|
|
|||
|
|
jwt.verify(token, secret, async (err, decoded) => {
|
|||
|
|
if (err) {
|
|||
|
|
if (err.name === "TokenExpiredError") {
|
|||
|
|
logger.warn(`令牌已过期: ${requestPath}`);
|
|||
|
|
return sendError(res, "令牌已过期", "Token expired", 401);
|
|||
|
|
}
|
|||
|
|
if (err.name === "JsonWebTokenError") {
|
|||
|
|
logger.warn(`无效的令牌: ${requestPath}`);
|
|||
|
|
return sendError(res, "无效的令牌", "Invalid token", 401);
|
|||
|
|
}
|
|||
|
|
logger.error("Token验证失败:", err);
|
|||
|
|
return sendError(res, "令牌验证失败", "Token verification failed", 401);
|
|||
|
|
}
|
|||
|
|
if (decoded.platform === 'PC') {
|
|||
|
|
const storedToken = await tokenStore.getToken(decoded.userId);
|
|||
|
|
if (storedToken !== token) {
|
|||
|
|
logger.warn(`令牌不匹配: ${requestPath}`);
|
|||
|
|
return sendError(res, "令牌不匹配", "Token does not match", 401);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 将解码后的用户信息添加到请求对象
|
|||
|
|
req.user = decoded;
|
|||
|
|
console.log(`用户 ${decoded.username || decoded.userId} 鉴权成功`);
|
|||
|
|
next();
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
logger.error("smartAuth中间件错误:", error);
|
|||
|
|
return sendError(res, "认证处理失败", error.message, 500);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建自定义智能鉴权中间件
|
|||
|
|
* 允许在路由级别指定额外的白名单 types
|
|||
|
|
* @param {string[]} additionalWhitelist - 额外的白名单 types
|
|||
|
|
* @returns {Function} 中间件函数
|
|||
|
|
*/
|
|||
|
|
const createSmartAuth = (additionalWhitelist = []) => {
|
|||
|
|
return (req, res, next) => {
|
|||
|
|
try {
|
|||
|
|
const requestPath = req.path;
|
|||
|
|
|
|||
|
|
// 检查路径是否在白名单中
|
|||
|
|
if (isPathInWhitelist(requestPath)) {
|
|||
|
|
console.log(`路径 ${requestPath} 在白名单中,跳过鉴权`);
|
|||
|
|
return next();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从请求参数中获取 type
|
|||
|
|
const type = req.body?.type || req.query?.type;
|
|||
|
|
|
|||
|
|
// 检查 type 是否在全局白名单或自定义白名单中
|
|||
|
|
if (
|
|||
|
|
type &&
|
|||
|
|
(isTypeInWhitelist(type) || additionalWhitelist.includes(type))
|
|||
|
|
) {
|
|||
|
|
console.log(`请求类型 ${type} 在白名单中,跳过鉴权`);
|
|||
|
|
return next();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 执行 JWT 验证(与 smartAuth 相同的逻辑)
|
|||
|
|
const authHeader = req.headers.authorization;
|
|||
|
|
|
|||
|
|
if (!authHeader) {
|
|||
|
|
logger.warn(`未授权访问: ${requestPath}, type: ${type}`);
|
|||
|
|
return sendError(res, "未提供认证令牌", "No token provided", 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const token = authHeader.startsWith("Bearer ")
|
|||
|
|
? authHeader.substring(7)
|
|||
|
|
: authHeader;
|
|||
|
|
|
|||
|
|
if (!token) {
|
|||
|
|
return sendError(res, "令牌格式错误", "Invalid token format", 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const secret = process.env.JWT_SECRET || "your-secret-key";
|
|||
|
|
|
|||
|
|
jwt.verify(token, secret, (err, decoded) => {
|
|||
|
|
if (err) {
|
|||
|
|
if (err.name === "TokenExpiredError") {
|
|||
|
|
logger.warn(`令牌已过期: ${requestPath}`);
|
|||
|
|
return sendError(res, "令牌已过期", "Token expired", 401);
|
|||
|
|
}
|
|||
|
|
if (err.name === "JsonWebTokenError") {
|
|||
|
|
logger.warn(`无效的令牌: ${requestPath}`);
|
|||
|
|
return sendError(res, "无效的令牌", "Invalid token", 401);
|
|||
|
|
}
|
|||
|
|
logger.error("Token验证失败:", err);
|
|||
|
|
return sendError(
|
|||
|
|
res,
|
|||
|
|
"令牌验证失败",
|
|||
|
|
"Token verification failed",
|
|||
|
|
401
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
req.user = decoded;
|
|||
|
|
console.log(`用户 ${decoded.username || decoded.userId} 鉴权成功`);
|
|||
|
|
next();
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
logger.error("createSmartAuth中间件错误:", error);
|
|||
|
|
return sendError(res, "认证处理失败", error.message, 500);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
smartAuth,
|
|||
|
|
createSmartAuth,
|
|||
|
|
};
|