NestJS:Passport策略中访问Execution Context报错‘context.getHandler is not a function’求解
问题:NestJS自定义RolesStrategy报错"context.getHandler is not a function"
在NestJS应用中使用Passport实现自定义RolesStrategy时,遇到错误:context.getHandler is not a function。原本预期Execution Context可以正常使用,需要排查问题原因及解决方法。
相关代码如下:
策略代码
import { Strategy } from 'passport-custom'; import { PassportStrategy } from '@nestjs/passport'; import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { ModuleRef, Reflector } from '@nestjs/core'; @Injectable() export class RolesStrategy extends PassportStrategy(Strategy, 'roles-strategy') { constructor(private readonly moduleRef: ModuleRef, private reflector: Reflector) { super(); } async validate(req: Request, context: ExecutionContext): Promise<Object> { if(req.headers['authorization']) { const token = req.headers['authorization']; const roles = this.reflector.get<string[]>('roles', context.getHandler()); } else { throw new UnauthorizedException(); } } }
API代码
@ApiSecurity('key') @UseGuards(AuthGuard(['admin-strategy', 'roles-strategy'])) @Roles('admin') @Get('/greet') async sayHello(@Headers() headers): Promise<any> { try { return { message: 'Hi' } ; } catch (error) { throw new InternalServerErrorException(error.message, error.status); } }
原因分析
passport-custom的validate方法默认仅接收**请求对象(req)**作为参数,你定义的第二个参数context并非NestJS的ExecutionContext实例,实际是undefined或无效值,因此调用context.getHandler()会抛出“not a function”错误。
Passport策略本身不支持直接接收NestJS的ExecutionContext,NestJS也不会自动将上下文对象传递给策略的validate方法。
解决方法
方法1:将角色校验逻辑移至自定义Guard(推荐)
将身份验证与角色校验分离:Passport策略负责验证token有效性并返回用户信息,自定义Guard负责通过Reflector获取路由元数据并校验角色。
- 自定义RolesGuard:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { // 获取路由上的@Roles元数据 const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler()); if (!requiredRoles) { return true; // 无角色要求时直接通过 } // 从请求中获取已验证的用户信息(由前面的AuthStrategy挂载) const request = context.switchToHttp().getRequest(); const user = request.user; // 校验用户角色是否包含所需角色 return requiredRoles.some(role => user.roles?.includes(role)); } }
- 调整API路由,同时使用AuthGuard和RolesGuard:
@ApiSecurity('key') @UseGuards(AuthGuard('admin-strategy'), RolesGuard) // 先验证身份,再校验角色 @Roles('admin') @Get('/greet') async sayHello(@Headers() headers): Promise<any> { try { return { message: 'Hi' }; } catch (error) { throw new InternalServerErrorException(error.message, error.status); } }
- 简化RolesStrategy,仅负责身份验证:
import { Strategy } from 'passport-custom'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common'; @Injectable() export class RolesStrategy extends PassportStrategy(Strategy, 'roles-strategy') { constructor() { super(); } async validate(req: Request): Promise<Object> { if (!req.headers['authorization']) { throw new UnauthorizedException(); } const token = req.headers['authorization']; // 这里添加token验证逻辑,返回包含角色信息的用户对象 const user = { id: 1, roles: ['admin'] }; return user; } }
方法2:在自定义AuthGuard中传递上下文到策略
如果需要在Strategy中使用ExecutionContext,可以自定义AuthGuard,将上下文挂载到请求对象上,再在Strategy中获取:
- 自定义AuthGuard:
import { AuthGuard } from '@nestjs/passport'; import { ExecutionContext, Injectable } from '@nestjs/common'; @Injectable() export class CustomAuthGuard extends AuthGuard(['admin-strategy', 'roles-strategy']) { getRequest(context: ExecutionContext) { const req = context.switchToHttp().getRequest(); // 将上下文挂载到request对象 req.context = context; return req; } }
- 修改Strategy的validate方法:
async validate(req: Request & { context: ExecutionContext }): Promise<Object> { if(req.headers['authorization']) { const token = req.headers['authorization']; const roles = this.reflector.get<string[]>('roles', req.context.getHandler()); // 这里添加角色校验逻辑 return {}; } else { throw new UnauthorizedException(); } }
- API路由使用自定义Guard:
@ApiSecurity('key') @UseGuards(CustomAuthGuard) @Roles('admin') @Get('/greet') async sayHello(@Headers() headers): Promise<any> { try { return { message: 'Hi' }; } catch (error) { throw new InternalServerErrorException(error.message, error.status); } }
内容的提问来源于stack exchange,提问作者Teknoville
相关产品推荐
相关产品推荐

