NestJS实现isAuthor守卫及OR逻辑权限校验的最佳方式
选型建议:优先使用守卫实现权限判断
- 这类资源权限校验场景选守卫(Guard),不要用中间件
- 核心原因:NestJS 中守卫的设计定位就是做权限/访问控制,执行时机在中间件之后、路由处理函数之前,天生可以拿到完整的执行上下文、路由参数、请求对象以及挂载的元数据,和框架的依赖注入体系完全适配,写资源归属判断的逻辑非常顺。而中间件执行时并不知道后续会匹配到哪个具体的路由处理方法,做资源级权限校验需要额外写很多路由解析、实例获取的冗余逻辑,完全没必要。
守卫注入服务失败的常见排查点
在守卫中注入服务失败基本都是以下几个配置疏漏,对照检查即可:
- 要注入的业务服务(比如查询文章内容的
PostsService)必须在所属模块的providers数组中声明;如果守卫在其他模块使用,需要将该服务加入所属模块的exports数组,同时在使用守卫的模块中导入服务所在的模块 - 守卫类必须加
@Injectable()装饰器,不能遗漏 - 不要手动
new守卫实例传入@UseGuards()装饰器,直接传守卫类的引用,让Nest依赖注入容器负责实例化,否则容器不会自动完成依赖注入
基础的isAuthor.guard参考写法:
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common'; import { PostsService } from '../posts/posts.service'; @Injectable() export class IsAuthorGuard implements CanActivate { constructor(private postsService: PostsService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const req = context.switchToHttp().getRequest(); // 前置auth守卫已经把登录用户信息挂载到req上,直接取用 const currentUserId = req.user.id; // 从路由参数中取目标资源ID,比如路由格式为 /posts/:id const targetPostId = req.params.id; const post = await this.postsService.findOne(targetPostId); if (post.authorId !== currentUserId) { throw new ForbiddenException('无权限操作该内容'); } return true; } }
守卫OR逻辑组合实现
NestJS 原生没有提供守卫逻辑组合的语法糖,但不需要每次都编写isAdminOrAuthor这类专用守卫,有两种成熟的轻量方案:
- 方案1:实现一个通用的逻辑聚合守卫,接收多个守卫类作为入参,内部依次执行每个守卫的
canActivate逻辑,只要任意一个守卫返回放行结果,就直接通过校验;所有守卫都校验不通过时再抛出权限异常。使用时直接写@UseGuards(AnyOf(AdminGuard, IsAuthorGuard))即可,后续要加新的权限判断规则只需要往括号里加守卫类就行。 - 方案2:通过自定义装饰器+单守卫的模式实现更灵活的规则配置。比如编写
@AccessControl()装饰器,把当前路由需要满足的权限规则(比如管理员、内容作者)作为参数写到装饰器里,守卫启动时读取当前路由挂载的权限规则,逐一校验用户是否满足其中任意一项,后续新增角色或者权限规则都不需要修改守卫逻辑,只需要调整装饰器传入的参数即可。
注意:守卫执行顺序为「全局守卫→控制器级守卫→路由级守卫」,已经实现的
auth.guard要放在校验链路的最前端,先判断用户登录状态,未登录直接拦截,避免后续权限逻辑做不必要的数据库查询。
内容的提问来源于stack exchange,提问作者Dora
相关产品推荐
相关产品推荐

