NestJs中CASL v6权限控制实现方案(Ability类已弃用)
NestJS中CASL v6权限控制的正确实现方式
CASL v6已弃用Ability类,以下是针对该版本在NestJS中实现权限控制的正确方案,先梳理之前的尝试问题:
之前的两次尝试
第一次实现
import { Injectable } from '@nestjs/common'; import { User } from 'src/user/entities/user.entity'; import { Ability, AbilityBuilder, AbilityClass, ExtractSubjectType, InferSubjects } from '@casl/ability'; import { Permissions } from 'src/shared'; type Subjects = InferSubjects<typeof User> | 'all'; export type AppAbility = Ability<[Permissions, Subjects]>; @Injectable() export class CaslAbilityFactory { createForUser(user: User) { const { can, cannot, build } = new AbilityBuilder< Ability<[Permissions, Subjects]> >(Ability as AbilityClass<AppAbility>); if (user.isSuperAdmin) { can(Permissions.Manage, 'all'); // 拥有所有读写权限 } else { can(Permissions.Read, 'all'); // 仅拥有所有只读权限 } // can(Permissions.Update, Article, { authorId: user.id }); // cannot(Permissions.Delete, Article, { isPublished: true }); return build({ detectSubjectType: (item) => item.constructor as ExtractSubjectType<Subjects>, }); } }
第二次实现
import { Injectable } from '@nestjs/common'; import { User } from 'src/user/entities/user.entity'; import { Ability, AbilityBuilder, AbilityClass, ExtractSubjectType, InferSubjects, PureAbility, } from '@casl/ability'; import { Permissions } from 'src/shared'; type Subjects = InferSubjects<typeof User> | 'all'; export type AppAbility = Ability<[Permissions, Subjects]>; @Injectable() export class CaslAbilityFactory { defineAbilityFor(user: User) { const { can, cannot, build } = new AbilityBuilder<AppAbility>(); if (user.isSuperAdmin) { can(Permissions.Manage, 'all'); // 拥有所有读写权限 } else { can(Permissions.Read, 'all'); // 仅拥有所有只读权限 } can(Permissions.Update, User, { id: user.id }); cannot(Permissions.Delete, User, { isPublished: true }); return build({ detectSubjectType: (item) => item.constructor as ExtractSubjectType<Subjects>, }); } }
尝试结果
- 第一种实现无报错,但因使用已弃用的
Ability类,会出现弃用警告 - 第二种实现报错,原因是CASL v6中
AbilityBuilder必须传入权限创建函数作为参数,代码中未传递
正确实现方案
CASL v6弃用了Ability类,改用createAbility(通用场景)或createMongoAbility(MongoDB场景)来创建权限实例,同时调整了AbilityBuilder的使用方式,以下是完整的正确实现:
1. 实现CaslAbilityFactory
import { Injectable } from '@nestjs/common'; import { User } from 'src/user/entities/user.entity'; import { createAbility, AbilityBuilder, ExtractSubjectType, InferSubjects, PureAbility } from '@casl/ability'; import { Permissions } from 'src/shared'; // 定义权限作用的主体类型 type Subjects = InferSubjects<typeof User> | 'all'; // 定义应用的权限类型 export type AppAbility = PureAbility<[Permissions, Subjects]>; @Injectable() export class CaslAbilityFactory { defineAbilityFor(user: User) { // 传入createAbility作为AbilityBuilder的参数 const { can, cannot, build } = new AbilityBuilder<AppAbility>(createAbility); if (user.isSuperAdmin) { can(Permissions.Manage, 'all'); // 超级管理员拥有所有权限 } else { can(Permissions.Read, 'all'); // 普通用户仅拥有全局只读权限 } // 允许用户修改自己的信息 can(Permissions.Update, User, { id: user.id }); // 禁止删除已发布的用户(示例规则) cannot(Permissions.Delete, User, { isPublished: true }); return build({ // 自动检测主体类型,用于权限匹配 detectSubjectType: (item) => item.constructor as ExtractSubjectType<Subjects>, }); } }
2. 创建CASL守卫(用于接口权限校验)
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { CaslAbilityFactory } from './casl-ability.factory'; import { AppAbility } from './casl-ability.factory'; import { Permissions } from 'src/shared'; @Injectable() export class CaslGuard implements CanActivate { constructor( private reflector: Reflector, private caslAbilityFactory: CaslAbilityFactory, ) {} async canActivate(context: ExecutionContext): Promise<boolean> { // 从控制器/方法元数据中获取所需权限 const requiredPermissions = this.reflector.getAllAndOverride<[Permissions, any]>( 'permissions', [context.getHandler(), context.getClass()], ); // 如果没有配置权限要求,直接放行 if (!requiredPermissions) { return true; } // 获取当前请求的用户信息 const { user } = context.switchToHttp().getRequest(); // 生成当前用户的权限实例 const ability = this.caslAbilityFactory.defineAbilityFor(user); // 校验用户是否拥有所需权限 return ability.can(...requiredPermissions); } }
3. 在控制器中使用权限守卫
import { Controller, Get, UseGuards, SetMetadata } from '@nestjs/common'; import { CaslGuard } from './casl.guard'; import { Permissions } from 'src/shared'; import { User } from './user.entity'; @Controller('users') export class UserController { @Get() @UseGuards(CaslGuard) // 设置接口所需权限:读取User类型数据 @SetMetadata('permissions', [Permissions.Read, User]) getUsers() { // 业务逻辑:返回用户列表 return []; } @Get('profile') @UseGuards(CaslGuard) // 设置接口所需权限:更新当前用户自身信息 @SetMetadata('permissions', [Permissions.Update, User]) getProfile() { // 业务逻辑:返回当前用户信息 return {}; } }
内容的提问来源于stack exchange,提问作者Favour George
相关产品推荐
相关产品推荐

