|
|
/**
|
|
|
* 身份认证和权限验证中间件
|
|
|
*
|
|
|
* 功能:
|
|
|
* 1. 验证用户是否已登录(JWT token验证)
|
|
|
* 2. 验证用户是否有足够的权限访问资源
|
|
|
*/
|
|
|
|
|
|
const { verifyToken } = require('../utils/encryption');
|
|
|
const { AuthenticationError, AuthorizationError } = require('./errorHandler');
|
|
|
|
|
|
/**
|
|
|
* 身份认证中间件
|
|
|
* 验证用户是否已登录(通过JWT token)
|
|
|
*
|
|
|
* 说明:
|
|
|
* - 前端在请求头中携带 token: "Bearer xxxxx"
|
|
|
* - 服务器验证token是否有效
|
|
|
* - 验证通过后,将用户信息存入 req.user
|
|
|
*
|
|
|
* 使用示例:
|
|
|
* router.get('/profile', authenticate, getProfile);
|
|
|
*/
|
|
|
const authenticate = async (req, res, next) => {
|
|
|
try {
|
|
|
// 从请求头获取token
|
|
|
const authHeader = req.headers.authorization;
|
|
|
|
|
|
// 检查是否提供了token
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
|
throw new AuthenticationError('未提供认证令牌,请先登录');
|
|
|
}
|
|
|
|
|
|
// 提取token(去掉"Bearer "前缀)
|
|
|
const token = authHeader.substring(7);
|
|
|
|
|
|
// 验证token
|
|
|
const decoded = verifyToken(token);
|
|
|
|
|
|
// 将解密后的用户信息存入req对象,供后续中间件使用
|
|
|
req.user = {
|
|
|
id: decoded.id,
|
|
|
username: decoded.username,
|
|
|
role: decoded.role
|
|
|
};
|
|
|
|
|
|
// 继续执行下一个中间件
|
|
|
next();
|
|
|
} catch (error) {
|
|
|
// 如果是我们定义的错误,直接传递
|
|
|
if (error instanceof AuthenticationError) {
|
|
|
next(error);
|
|
|
} else {
|
|
|
// 其他错误统一处理为认证失败
|
|
|
next(new AuthenticationError(error.message));
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 角色权限验证中间件工厂函数
|
|
|
* 生成一个验证特定角色权限的中间件
|
|
|
*
|
|
|
* @param {array} allowedRoles - 允许访问的角色列表
|
|
|
* @returns {function} Express中间件
|
|
|
*
|
|
|
* 说明:
|
|
|
* - 必须在 authenticate 之后使用
|
|
|
* - 检查用户角色是否在允许的角色列表中
|
|
|
*
|
|
|
* 使用示例:
|
|
|
* // 只允许管理员访问
|
|
|
* router.delete('/users/:id', authenticate, checkRole(['admin']), deleteUser);
|
|
|
*
|
|
|
* // 允许管理员和服务员访问
|
|
|
* router.post('/orders', authenticate, checkRole(['admin', 'waiter']), createOrder);
|
|
|
*/
|
|
|
const checkRole = (allowedRoles) => {
|
|
|
return (req, res, next) => {
|
|
|
try {
|
|
|
// 确保已经通过身份认证
|
|
|
if (!req.user) {
|
|
|
throw new AuthenticationError('请先登录');
|
|
|
}
|
|
|
|
|
|
// 检查用户角色是否在允许列表中
|
|
|
if (!allowedRoles.includes(req.user.role)) {
|
|
|
throw new AuthorizationError(`此操作需要以下角色之一: ${allowedRoles.join(', ')}`);
|
|
|
}
|
|
|
|
|
|
// 权限验证通过,继续执行
|
|
|
next();
|
|
|
} catch (error) {
|
|
|
next(error);
|
|
|
}
|
|
|
};
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 可选的身份认证中间件
|
|
|
* 如果提供了token就验证,没提供也不报错
|
|
|
*
|
|
|
* 说明:适用于"登录后有额外功能,不登录也能访问"的场景
|
|
|
* 例如:浏览菜单不需要登录,但登录后可以看到会员价格
|
|
|
*
|
|
|
* 使用示例:
|
|
|
* router.get('/menu', optionalAuthenticate, getMenu);
|
|
|
*/
|
|
|
const optionalAuthenticate = async (req, res, next) => {
|
|
|
try {
|
|
|
const authHeader = req.headers.authorization;
|
|
|
|
|
|
// 如果没有提供token,直接跳过
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
|
req.user = null;
|
|
|
return next();
|
|
|
}
|
|
|
|
|
|
// 如果提供了token,验证它
|
|
|
const token = authHeader.substring(7);
|
|
|
const decoded = verifyToken(token);
|
|
|
|
|
|
req.user = {
|
|
|
id: decoded.id,
|
|
|
username: decoded.username,
|
|
|
role: decoded.role
|
|
|
};
|
|
|
|
|
|
next();
|
|
|
} catch (error) {
|
|
|
// token无效也不报错,只是不设置用户信息
|
|
|
req.user = null;
|
|
|
next();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 检查是否是资源所有者或管理员
|
|
|
* 用于保护用户只能操作自己的资源
|
|
|
*
|
|
|
* @param {string} userIdParam - URL参数中用户ID的字段名
|
|
|
* @returns {function} Express中间件
|
|
|
*
|
|
|
* 使用示例:
|
|
|
* // 用户只能查看自己的订单,或者管理员可以查看所有订单
|
|
|
* router.get('/users/:userId/orders', authenticate, checkOwnerOrAdmin('userId'), getOrders);
|
|
|
*/
|
|
|
const checkOwnerOrAdmin = (userIdParam = 'id') => {
|
|
|
return (req, res, next) => {
|
|
|
try {
|
|
|
if (!req.user) {
|
|
|
throw new AuthenticationError('请先登录');
|
|
|
}
|
|
|
|
|
|
const resourceUserId = parseInt(req.params[userIdParam]);
|
|
|
const currentUserId = req.user.id;
|
|
|
const isAdmin = req.user.role === 'admin';
|
|
|
|
|
|
// 如果是管理员,或者是资源所有者,允许访问
|
|
|
if (isAdmin || currentUserId === resourceUserId) {
|
|
|
next();
|
|
|
} else {
|
|
|
throw new AuthorizationError('您只能访问自己的资源');
|
|
|
}
|
|
|
} catch (error) {
|
|
|
next(error);
|
|
|
}
|
|
|
};
|
|
|
};
|
|
|
|
|
|
// 导出所有中间件
|
|
|
module.exports = {
|
|
|
authenticate,
|
|
|
checkRole,
|
|
|
optionalAuthenticate,
|
|
|
checkOwnerOrAdmin
|
|
|
};
|