You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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() {
    // 删除用户逻辑
  }
}

五、动态配置能力落地

  1. 新增权限:直接向permissions表插入新记录,无需修改代码
  2. 分配/回收权限:通过后台接口操作user_permissions中间表,维护用户与权限的关联关系
  3. 性能优化:将用户权限列表缓存到Redis(有效期设为1-2小时),避免频繁查询数据库;权限变更时主动清空对应缓存

六、扩展优化建议

  • 若需更细粒度的资源级权限,可扩展Permission实体,添加resource(如user、post)和action(如create、delete)字段,在守卫中实现更灵活的匹配逻辑
  • 结合JWT使用时,可将用户权限列表存入JWT Payload,但需注意权限变更后JWT不会立即失效,需配合刷新令牌或缓存失效机制处理

内容的提问来源于stack exchange,提问作者TalVik99

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 12:02:03