如何在NestJS 10中构建验证邮箱已确认的Guard
实现邮箱验证Guard的步骤
1. 更新JWT载荷,加入邮箱验证状态
首先需要在生成JWT令牌时,将用户的isEmailConfirmed属性加入到JWT载荷中。这样Guard可以直接从令牌中获取该状态(优点是性能好,缺点是用户验证邮箱后,旧令牌依然有效直到过期)。
修改getTokens方法
先通过用户ID获取用户的邮箱验证状态,再将其加入JWT payload:
// 注入UserService用于查询用户信息 constructor( private readonly jwtService: JwtService, private readonly configService: ConfigService, private readonly userService: UserService, ) {} async getTokens(userId: string, username: string) { // 获取用户的邮箱验证状态 const user = await this.userService.findOneById(userId); const isEmailConfirmed = user.isEmailConfirmed; const [accessToken, refreshToken] = await Promise.all([ this.jwtService.signAsync( { sub: userId, username, isEmailConfirmed, // 新增邮箱验证状态字段 }, { secret: this.configService.get<string>('JWT_ACCESS_SECRET'), expiresIn: ACCESS_TOKEN_DURATION, }, ), this.jwtService.signAsync( { sub: userId, username, isEmailConfirmed, // 刷新令牌同步加入该字段 }, { secret: this.configService.get<string>('JWT_REFRESH_SECRET'), expiresIn: REFRESH_TOKEN_DURATION, }, ), ]); return { accessToken, refreshToken, }; }
更新AccessTokenStrategy的载荷类型
修改JwtPayload类型,新增isEmailConfirmed字段:
type JwtPayload = { sub: string; username: string; isEmailConfirmed: boolean; // 新增字段 }; @Injectable() export class AccessTokenStrategy extends PassportStrategy(Strategy, 'jwt') { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: process.env.JWT_ACCESS_SECRET, }); } validate(payload: JwtPayload) { return payload; // 验证后的载荷会注入到请求对象的user属性中 } }
2. 创建邮箱验证Guard
自定义Guard继承AuthGuard('jwt'),先确保用户已登录,再检查邮箱验证状态:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class EmailConfirmedGuard extends AuthGuard('jwt') { async canActivate(context: ExecutionContext): Promise<boolean> { // 先调用父类方法确保用户已登录 const isAuthenticated = await super.canActivate(context); if (!isAuthenticated) { return false; } // 获取请求中的用户信息 const request = context.switchToHttp().getRequest(); const user = request.user; // 校验邮箱是否已验证 return user.isEmailConfirmed === true; } }
3. 使用Guard保护接口
可以在单个路由或整个控制器上使用该Guard:
单个路由使用
import { Controller, Get, UseGuards } from '@nestjs/common'; import { EmailConfirmedGuard } from './email-confirmed.guard'; @Controller('protected') export class ProtectedController { @Get() @UseGuards(EmailConfirmedGuard) getProtectedData() { return { message: '只有邮箱已验证的用户才能访问此接口' }; } }
整个控制器使用
@Controller('protected') @UseGuards(EmailConfirmedGuard) export class ProtectedController { @Get() getProtectedData() { return { message: '控制器下所有接口都需要邮箱验证' }; } }
可选方案:实时查询数据库验证
如果需要用户验证邮箱后立即生效(不等待JWT过期),可以在Guard中查询数据库获取最新状态:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { UserService } from '../user/user.service'; @Injectable() export class EmailConfirmedGuard extends AuthGuard('jwt') { constructor(private readonly userService: UserService) { super(); } async canActivate(context: ExecutionContext): Promise<boolean> { const isAuthenticated = await super.canActivate(context); if (!isAuthenticated) { return false; } const request = context.switchToHttp().getRequest(); const userId = request.user.sub; // 查询数据库获取最新用户状态 const user = await this.userService.findOneById(userId); return user.isEmailConfirmed === true; } }
该方案优点是状态实时更新,缺点是每次请求都要查询数据库,性能略有下降,适合对实时性要求高的场景。
内容的提问来源于stack exchange,提问作者Dave
相关产品推荐
相关产品推荐

