范围:src/gateway/auth/, src/business/auth/, src/app.module.ts 涉及文件: - 新增:src/gateway/auth/ 目录及所有文件 - 移动:Controller、Guard、Decorator、DTO从business层移至gateway层 - 修改:src/business/auth/index.ts(移除Gateway层组件导出) - 修改:src/app.module.ts(使用AuthGatewayModule替代AuthModule) 主要改进: - 明确Gateway层和Business层的职责边界 - Controller、Guard、Decorator属于Gateway层职责 - Business层专注于业务逻辑和服务 - 符合分层架构设计原则
119 lines
3.5 KiB
TypeScript
119 lines
3.5 KiB
TypeScript
/**
|
||
* 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<boolean> 认证是否成功
|
||
* @throws UnauthorizedException 当令牌缺失或无效时
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* @Get('protected')
|
||
* @UseGuards(JwtAuthGuard)
|
||
* getProtectedData() {
|
||
* // 此方法需要有效的JWT令牌才能访问
|
||
* }
|
||
* ```
|
||
*/
|
||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||
const request = context.switchToHttp().getRequest<Request>();
|
||
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;
|
||
}
|
||
} |