NestJS:自定义JWT Guard抛异常至Filter,返回自定义UnauthorizedException
问题描述
我编写了一个自定义JWT Guard,当检测到请求Cookie中缺少access token时会抛出错误。当前返回的是ForbiddenException,我希望向React客户端返回带有自定义消息的UnauthorizedException。请问从Guard抛出异常并在Filter中捕获的正确方式是什么?是否有更优实现方案?
相关代码及日志
JwtAuthGuard.ts
const cookieExtractor = (req: Request) => { if (req && req.cookies) { return req.cookies['jwt']; } }; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') { private logger = new Logger(JwtAuthGuard.name); constructor() { super(); } async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); try { const accessToken = ExtractJwt.fromExtractors([cookieExtractor])(request); if (!accessToken) throw new UnauthorizedException('Access token is not set'); return this.activate(context); } catch (err) { this.logger.error((err as Error).message); return false; } } async activate(context: ExecutionContext): Promise<boolean> { return super.canActivate(context) as Promise<boolean>; } }
AuthExceptionFilter.ts
@Catch(HttpException) export class AuthExceptionFilter extends BaseExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { console.log('exception', exception); const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception.getStatus(); response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, }); super.catch(exception, host); } }
Controller.ts
@Get('/status') @UseGuards(JwtAuthGuard) @UseFilters(AuthExceptionFilter) async status(@Req() req: Request) { return true; }
控制台异常日志
[Nest] 65933 - 02/24/2023, 10:44:38 AM ERROR [JwtAuthGuard] Access token is not set exception ForbiddenException: Forbidden resource at canActivateFn (/test-project/node_modules/@nestjs/core/router/router-execution-context.js:136:23) at processTicksAndRejections (node:internal/process/task_queues:95:5) at /test-project/node_modules/@nestjs/core/router/router-execution-context.js:42:31 at /test-project/node_modules/@nestjs/core/router/router-proxy.js:9:17 { response: { statusCode: 403, message: 'Forbidden resource', error: 'Forbidden' }, status: 403, options: {} }
问题原因
当前Guard的canActivate方法中捕获异常后返回false,Nest框架会自动将返回false的Guard校验结果转换为ForbiddenException,导致你自定义抛出的UnauthorizedException被覆盖,最终Filter接收到的是默认的403异常。
修复方案
1. 修正Guard的异常处理逻辑
不要在canActivate中返回false,而是直接抛出异常,或在catch块中重新抛出异常:
async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); try { const accessToken = ExtractJwt.fromExtractors([cookieExtractor])(request); if (!accessToken) { throw new UnauthorizedException('Access token is not set'); } return super.canActivate(context) as Promise<boolean>; } catch (err) { this.logger.error((err as Error).message); // 重新抛出异常,不要返回false throw err; } }
若不需要日志,可简化为:
async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const accessToken = ExtractJwt.fromExtractors([cookieExtractor])(request); if (!accessToken) { throw new UnauthorizedException('Access token is not set'); } return super.canActivate(context) as Promise<boolean>; }
2. 调整Exception Filter,返回自定义消息
修改Filter,将异常的自定义消息加入响应体,同时避免重复发送响应(去掉super.catch):
@Catch(HttpException) export class AuthExceptionFilter extends BaseExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception.getStatus(); const exceptionResponse = exception.getResponse(); // 提取异常消息,兼容字符串和对象格式 const message = typeof exceptionResponse === 'string' ? exceptionResponse : (exceptionResponse as { message: string | string[] }).message; response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: message // 加入自定义消息 }); } }
更优实现方案
利用Nest JWT模块原生能力,无需手动判断token是否存在:
1. 在Guard中配置Token提取器
直接在Guard构造函数中通过super配置JWT提取器,让框架自动处理token缺失逻辑:
@Injectable() export class JwtAuthGuard extends AuthGuard('jwt') { private logger = new Logger(JwtAuthGuard.name); constructor() { super({ // 指定从Cookie提取token jwtFromRequest: ExtractJwt.fromExtractors([cookieExtractor]), }); } // 重写handleRequest方法,自定义异常消息 handleRequest(err: any, user: any, info: any) { if (err || !user) { // 根据JWT模块的错误信息返回自定义提示 throw err || new UnauthorizedException(info?.message || 'Access token is missing or invalid'); } return user; } }
2. 移除手动token校验
此时canActivate方法可完全复用父类逻辑,无需自己实现,进一步简化代码。框架会自动检测token是否存在,缺失时抛出UnauthorizedException,配合自定义Filter即可返回带自定义消息的响应。
内容的提问来源于stack exchange,提问作者asus
相关产品推荐
相关产品推荐

