From c0b2ab95880ee52ea7666149ad77783039fd1664 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Thu, 2 Jul 2026 11:29:28 +0800 Subject: [PATCH] feat(rbac): add PermissionGuard, @Public and @RequirePermission decorators --- .../auth/decorators/permission.decorator.ts | 11 ++++++ .../src/auth/decorators/public.decorator.ts | 4 +++ backend/src/auth/guards/permission.guard.ts | 34 +++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 backend/src/auth/decorators/permission.decorator.ts create mode 100644 backend/src/auth/decorators/public.decorator.ts create mode 100644 backend/src/auth/guards/permission.guard.ts diff --git a/backend/src/auth/decorators/permission.decorator.ts b/backend/src/auth/decorators/permission.decorator.ts new file mode 100644 index 0000000..0bbd393 --- /dev/null +++ b/backend/src/auth/decorators/permission.decorator.ts @@ -0,0 +1,11 @@ +import { SetMetadata } from '@nestjs/common'; + +export const PERMISSION_KEY = 'permissions'; + +/** + * 声明接口所需权限。 + * 多次调用 = AND 逻辑(需要同时满足所有组); + * 单次调用多个参数 = OR 逻辑(满足其中一个即可)。 + */ +export const RequirePermission = (...permissions: string[]) => + SetMetadata(PERMISSION_KEY, permissions); diff --git a/backend/src/auth/decorators/public.decorator.ts b/backend/src/auth/decorators/public.decorator.ts new file mode 100644 index 0000000..b3845e1 --- /dev/null +++ b/backend/src/auth/decorators/public.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common'; + +export const IS_PUBLIC_KEY = 'isPublic'; +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/backend/src/auth/guards/permission.guard.ts b/backend/src/auth/guards/permission.guard.ts new file mode 100644 index 0000000..bda1859 --- /dev/null +++ b/backend/src/auth/guards/permission.guard.ts @@ -0,0 +1,34 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { IS_PUBLIC_KEY } from '../decorators/public.decorator'; +import { PERMISSION_KEY } from '../decorators/permission.decorator'; + +@Injectable() +export class PermissionGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + // 1. @Public() 豁免 + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (isPublic) return true; + + // 2. 获取所需权限(getAllAndMerge 合并 handler+class 层的所有 metadata) + const requiredPermissions = this.reflector.getAllAndMerge( + PERMISSION_KEY, + [context.getHandler(), context.getClass()], + ); + // 无装饰器 = 默认拒绝 + if (!requiredPermissions || requiredPermissions.length === 0) return false; + + // 3. 从 JWT payload 获取用户权限 + const request = context.switchToHttp().getRequest(); + const user = request.user; + if (!user || !user.permissions || !Array.isArray(user.permissions)) return false; + + // 4. OR 匹配:用户拥有 requiredPermissions 中任一权限即可通过 + return requiredPermissions.some(p => user.permissions.includes(p)); + } +}