在 NestJS 框架中,Guard
是一种强大的中间件机制,用于在执行控制器方法之前对 HTTP 请求进行预处理与验证。本篇博客将详细介绍一个名为 GlobalGuard
的全局 Guard 实现,该 Guard 负责检查请求的认证信息,并根据需要调用 AuthService
里的方法进行令牌验证。以下是对关键代码段的详细解读。
一、依赖注入与类定义
@Injectable()
export class GlobalGuard implements CanActivate {
constructor(
private readonly authService: AuthService,
private readonly reflector: Reflector
) {}
首先,GlobalGuard
被声明为一个 @Injectable()
装饰器修饰的类,表明它是一个可供依赖注入系统管理的服务。同时,GlobalGuard
实现了 NestJS 提供的 CanActivate
接口,这意味着它具备了拦截 HTTP 请求并在其到达控制器方法前进行预处理的能力。
在构造函数中,我们注入了两个依赖:
-
AuthService: 用于处理与用户认证相关的逻辑,如令牌验证。这里假设
AuthService
提供了一个validateToken
方法,用于验证并返回用户信息。(这里的方法需要自己写。) -
Reflector: NestJS 提供的反射工具,用于获取控制器方法、路由等上的元数据,便于动态调整 Guard 的行为。
二、实现 canActivate 方法
async canActivate(context: ExecutionContext): Promise<boolean> {
// ... 具体实现 ...
}
canActivate
是 CanActivate
接口要求实现的方法,接收一个ExecutionContext
参数,表示当前正在处理的 HTTP
请 求上下文。此方法必须返回一个 Promise<boolean>
,表示是否允许请求继续执行对应的控制器方法。接下来,我们将详细解析 canActivate
的内部逻辑。
1. 检查请求是否公开
const isPublic = this.reflector.get<boolean>(
"isPublic",
context.getHandler()
);
if (isPublic) {
return true;
}
使用 reflector
从当前请求对应的控制器方法上获取名为 isPublic
的元数据。如果该元数据存在且为 true
,说明该请求无需认证即可访问,Guard
直接允许请求通过。
2. 检查认证头
const authorizationHeader = request.headers.authorization;
if (!authorizationHeader) {
throw new HttpException("缺少认证头。", HttpStatus.UNAUTHORIZED);
}
const [type, token] = authorizationHeader.split(" ");
if (type !== "Bearer" || !token) {
throw new HttpException("认证头格式错误。", HttpStatus.UNAUTHORIZED);
}
首先从请求头中提取 authorization
字段。若该字段不存在,Guard 抛出一个 HttpException
,状态码为 UNAUTHORIZED(401)
,提示“缺少认证头”。
接着,对 authorization
字段进行拆分,期望得到形如 Bearer <token>
的格式。若拆分后的类型不是 Bearer
或没有有效的令牌,Guard
再次抛出 HttpException
,状态码为 UNAUTHORIZED
,提示“认证头格式错误”。
3. 验证令牌并设置用户数据
try {
request.userData = await this.authService.validateToken(token);
return true;
} catch (error) {
throw new HttpException("无效或过期的令牌。", HttpStatus.UNAUTHORIZED);
}
调用 AuthService
的 validateToken
方法,传入提取的令牌进行验证。如果验证成功,将返回的用户信息存储在 request.userData
中,以便后续控制器方法使用。Guard
最后返回 true
,允许请求继续执行。
若 validateToken
方法抛出异常(通常表示令牌无效或已过期),Guard
捕获该异常并抛出一个新的 HttpException
,状态码为UNAUTHORIZED
,提示“无效或过期的令牌”。
结语
至此,我们详细剖析了 NestJS 项目中 GlobalGuard
的实现细节。该 Guard
作为全局认证防护层,有效地检查了 HTTP 请求的认证头,利用 AuthService
的方法来验证令牌,并根据验证结果决定是否允许请求继续执行。同时,通过 Reflector
支持灵活地为特定请求方法标记为无需认证,体现了 NestJS Guard 机制的强大与灵活性。在实际项目中,这样的全局 Guard 能够极大地简化认证相关的代码组织与维护工作。
完整代码
import {
Injectable,
CanActivate,
ExecutionContext,
HttpException,
HttpStatus,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { AuthService } from "src/auth/auth.service";
@Injectable()
export class GlobalGuard implements CanActivate {
constructor(
private readonly authService: AuthService,
private readonly reflector: Reflector
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const authorizationHeader = request.headers.authorization;
// 检查当前请求是否标记为公开(无需认证),公开则直接通过。
const isPublic = this.reflector.get<boolean>(
"isPublic",
context.getHandler()
);
if (isPublic) {
return true;
}
if (!authorizationHeader) {
throw new HttpException("缺少认证头。", HttpStatus.UNAUTHORIZED);
}
const [type, token] = authorizationHeader.split(" ");
if (type !== "Bearer" || !token) {
throw new HttpException("认证头格式错误。", HttpStatus.UNAUTHORIZED);
}
try {
request.userData = await this.authService.validateToken(token); // 假设 validateToken 方法用于验证并返回用户信息
return true;
} catch (error) {
throw new HttpException("无效或过期的令牌。", HttpStatus.UNAUTHORIZED);
}
}
}