/** * JWT 认证守卫 * * 功能描述: * - 验证请求中的 JWT 令牌 * - 提取用户信息并添加到请求上下文 * - 保护需要认证的路由 * * 职责分离: * - 专注于JWT令牌验证和用户认证 * - 提供统一的认证守卫机制 * - 处理认证失败的异常情况 * * 最近修改: * - 2026-01-07: 代码规范优化 - 文件夹扁平化,移除单文件文件夹结构 * - 2026-01-07: 代码规范优化 - 文件重命名为snake_case格式,更新注释规范 * * @author moyin * @version 1.0.2 * @since 2025-01-05 * @lastModified 2026-01-07 */ import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, Logger, } from '@nestjs/common'; import { Request } from 'express'; import { LoginCoreService, JwtPayload } from '../../core/login_core/login_core.service'; /** * 扩展的请求接口,包含用户信息 */ export interface AuthenticatedRequest extends Request { user: JwtPayload; } @Injectable() export class JwtAuthGuard implements CanActivate { private readonly logger = new Logger(JwtAuthGuard.name); constructor(private readonly loginCoreService: LoginCoreService) {} /** * JWT令牌验证和用户认证 * * 业务逻辑: * 1. 从请求头中提取Bearer令牌 * 2. 验证令牌的有效性和签名 * 3. 解码令牌获取用户信息 * 4. 将用户信息添加到请求上下文 * 5. 记录认证成功或失败的日志 * 6. 返回认证结果 * * @param context 执行上下文,包含HTTP请求信息 * @returns Promise 认证是否成功 * @throws UnauthorizedException 当令牌缺失或无效时 * * @example * ```typescript * @Get('protected') * @UseGuards(JwtAuthGuard) * getProtectedData() { * // 此方法需要有效的JWT令牌才能访问 * } * ``` */ async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request); if (!token) { this.logger.warn('访问被拒绝:缺少认证令牌'); throw new UnauthorizedException('缺少认证令牌'); } try { // 使用Core层服务验证JWT令牌 const payload = await this.loginCoreService.verifyToken(token, 'access'); // 将用户信息添加到请求对象 (request as AuthenticatedRequest).user = payload; this.logger.log(`用户认证成功: ${payload.username} (ID: ${payload.sub})`); return true; } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误'; this.logger.warn(`JWT 令牌验证失败: ${errorMessage}`); throw new UnauthorizedException('无效的认证令牌'); } } /** * 从请求头中提取JWT令牌 * * 业务逻辑: * 1. 获取Authorization请求头 * 2. 解析Bearer令牌格式 * 3. 验证令牌类型是否为Bearer * 4. 返回提取的令牌字符串 * * @param request HTTP请求对象 * @returns string | undefined JWT令牌字符串或undefined * @throws 无异常抛出,返回undefined表示令牌不存在 * * @example * ```typescript * // 请求头格式:Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... * const token = this.extractTokenFromHeader(request); * ``` */ private extractTokenFromHeader(request: Request): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } }