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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 10:52:21