NestJS权限存储方案咨询:动态权限如何适配装饰器与守卫?
基于NestJS的动态声明式授权实现方案
一、数据库结构设计
你的多对多关联思路完全可行,这里给出优化后的实体设计(以TypeORM为例),支持动态权限配置:
1. Permission 权限实体
// permission.entity.ts import { Entity, Column, PrimaryGeneratedColumn, ManyToMany, JoinTable } from 'typeorm'; import { User } from './user.entity'; @Entity() export class Permission { @PrimaryGeneratedColumn() id: number; @Column({ unique: true, comment: '权限标识,如user:create、post:delete' }) name: string; @Column({ comment: '权限描述' }) description: string; @ManyToMany(() => User, user => user.permissions) @JoinTable({ name: 'user_permissions' }) users: User[]; }
2. User 用户实体
// user.entity.ts import { Entity, Column, PrimaryGeneratedColumn, ManyToMany } from 'typeorm'; import { Permission } from './permission.entity'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() username: string; @ManyToMany(() => Permission, permission => permission.users) permissions: Permission[]; }
二、自定义权限装饰器
创建装饰器标记接口所需的权限,支持传入单个或多个权限标识:
// require-permissions.decorator.ts import { SetMetadata } from '@nestjs/common'; export const REQUIRE_PERMISSIONS_KEY = 'requirePermissions'; export const RequirePermissions = (...permissions: string[]) => SetMetadata(REQUIRE_PERMISSIONS_KEY, permissions);
三、实现权限校验守卫
核心逻辑是从数据库(或缓存)获取当前用户权限,与装饰器标记的权限做对比:
// permissions.guard.ts import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { REQUIRE_PERMISSIONS_KEY } from './require-permissions.decorator'; import { UserService } from '../user/user.service'; @Injectable() export class PermissionsGuard implements CanActivate { constructor( private reflector: Reflector, private userService: UserService, ) {} async canActivate(context: ExecutionContext): Promise<boolean> { // 获取接口要求的权限列表 const requiredPermissions = this.reflector.getAllAndOverride<string[]>( REQUIRE_PERMISSIONS_KEY, [context.getHandler(), context.getClass()], ); if (!requiredPermissions) return true; // 无权限要求的接口直接放行 // 从请求中获取已认证的用户(需提前通过AuthGuard完成用户解析) const request = context.switchToHttp().getRequest(); const user = request.user; // 查询用户关联的所有权限(可添加Redis缓存优化查询性能) const userWithPermissions = await this.userService.findOne(user.id, { relations: ['permissions'] }); const userPermissions = userWithPermissions.permissions.map(p => p.name); // 校验用户是否拥有所有必需权限(可根据需求改为"拥有任一即可") return requiredPermissions.every(perm => userPermissions.includes(perm)); } }
四、守卫的使用方式
1. 全局注册(所有接口默认生效)
在AppModule中配置全局守卫:
// app.module.ts import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { PermissionsGuard } from './auth/permissions.guard'; @Module({ providers: [ { provide: APP_GUARD, useClass: PermissionsGuard, }, ], }) export class AppModule {}
2. 局部使用(仅指定接口/控制器生效)
直接在控制器或接口方法上添加守卫:
// user.controller.ts import { Controller, Post, UseGuards } from '@nestjs/common'; import { RequirePermissions } from '../auth/require-permissions.decorator'; import { PermissionsGuard } from '../auth/permissions.guard'; @Controller('users') @UseGuards(PermissionsGuard) export class UserController { @Post() @RequirePermissions('user:create') createUser() { // 创建用户逻辑 } @Post(':id/delete') @RequirePermissions('user:delete', 'user:manage') deleteUser() { // 删除用户逻辑 } }
五、动态配置能力落地
- 新增权限:直接向
permissions表插入新记录,无需修改代码 - 分配/回收权限:通过后台接口操作
user_permissions中间表,维护用户与权限的关联关系 - 性能优化:将用户权限列表缓存到Redis(有效期设为1-2小时),避免频繁查询数据库;权限变更时主动清空对应缓存
六、扩展优化建议
- 若需更细粒度的资源级权限,可扩展
Permission实体,添加resource(如user、post)和action(如create、delete)字段,在守卫中实现更灵活的匹配逻辑 - 结合JWT使用时,可将用户权限列表存入JWT Payload,但需注意权限变更后JWT不会立即失效,需配合刷新令牌或缓存失效机制处理
内容的提问来源于stack exchange,提问作者TalVik99
相关产品推荐
相关产品推荐

