- 认证鉴权
【免费下载链接】casl
CASL is an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access
@casl/ability是 CASL 权限库的核心包,提供了权限规则的定义、存储、检索与判断的一整套运行时 API。本文以官方 API 参考文档(docs-src/src/content/pages/api/casl-ability/en.md)为骨架,结合仓库内 packages/casl-ability/src 的实际源码实现,逐项讲解Ability、MongoAbility、AbilityBuilder、defineAbility、ForbiddenError以及各 matcher 与工具函数的签名、参数、返回值与典型用法。读完本文,你将能够独立使用这套 API 构建类型安全的权限模型、编写可测试的授权逻辑,并在前端框架或后端服务中正确集成动态权限更新。
包结构与模块划分
@casl/ability从入口(packages/casl-ability/src/index.ts)对外暴露两个模块:
- core 模块:提供
Ability及其他核心类,通过import * as core from '@casl/ability'引入; - extra 模块:提供额外的辅助函数(如
packRules、permittedFieldsOf、rulesToFields等),通过import * as extra from '@casl/ability/extra'引入。
import * as core from '@casl/ability'; import * as extra from '@casl/ability/extra';本文只讲解 core 包的内容;extra 包的辅助函数说明见 @casl/ability/extra API。文中所有示例均使用 TypeScript。
Ability:权限检查的基类
Ability是权限检查功能的基类(定义见 packages/casl-ability/src/Ability.ts),它继承自RuleIndex(见 packages/casl-ability/src/RuleIndex.ts),负责对规则做索引、合并与匹配。它是一个泛型类,接受 2 个类型参数:
Abilities:要么是一个字符串字面量类型(代表所有可能的 action),要么是一个由两个元素组成的元组(代表所有可能的 action 与 subject 组合)。默认值为[string, Subject];Conditions:条件的形状,没有类型限制,可以是任意类型。
import { Ability, Abilities } from '@casl/ability'; type ClaimAbility = Ability<string>; type AppAbility = Ability<[string, string]>;可以看到,ClaimAbility只关心 action(适合做“声明式权限”场景,例如 API 能力标记),而AppAbility同时约束 action 与 subject(适合常规的基于资源的授权)。
Ability 构造函数
new Ability<A, Conditions>(rules, options)(下文用A简写Abilities):
rules: RawRuleFrom<A, Conditions>[] = []:初始规则数组,默认为空。每条规则的结构定义在 packages/casl-ability/src/RawRule.ts:action(必填,可以是单个 action 或数组)、subject(可选,单个 subject 类型或数组)、fields(字段白名单)、conditions(条件对象)、inverted(是否为否定规则)、reason(规则不允许时的原因说明)。options: AbilityOptions<A, Conditions> = {}:可选配置项,各选项含义如下:detectSubjectType?: (subject?: Subject) => string:自定义 subject 类型检测逻辑,参见 subject 类型检测指南;conditionsMatcher?: ConditionsMatcher<Conditions>:自定义条件匹配语言,参见 自定义 Ability;fieldMatcher?: FieldMatcher:自定义字段匹配逻辑,参见 自定义字段匹配器;resolveAction?: ResolveAction<Normalize<A>[0]>:传入一个把别名解析为真实 action 的函数,参见 定义 action 别名。
基础用法:
const ability = new Ability<['read' | 'update', 'Article']>([ { action: 'read', subject: 'Article' }, { action: 'update', subject: 'Article' }, ]);在 packages/casl-ability/src/RuleIndex.ts 中可以看到构造函数还会读取两个隐含默认值:anyAction默认为'manage'(通配 action),anySubjectType默认为'all'(通配 subject)。因此{ action: 'manage', subject: 'all' }表示“对任意 subject 拥有任意权限”。
更完整的规则定义方式参见 定义规则指南。
update:整体替换规则
update(rules)方法会完全替换实例上之前定义的所有规则:
- 参数:
rules: RawRuleFrom<A, Conditions>[]; - 返回值:
this(便于链式调用); - 触发事件:更新前触发
update,更新后触发updated。
典型场景是登录、登出或用户权限变更时刷新权限。从 RuleIndex 源码 可见其实现先发出update事件,重置字段级规则标记,保存新规则并重建索引,最后发出updated事件:
import { Ability } from '@casl/ability'; const ability = new Ability([{ action: 'manage', subject: 'all' }]); ability.update([]); // 收回所有权限入门用法可参见 CASL 指南。
can:权限正向判断
can(...)检查给定的 action 与 subject 是否满足权限。根据Abilities泛型的不同,它接受 1 个参数(当Abilities是字符串时,只传 action)或 2~3 个参数(当Abilities是元组时):
- 参数:
action: string(要检查的 action);subject: Subject(要检查的 subject);field?: string(要检查的字段,可选); - 返回值:
boolean。
import { Ability } from '@casl/ability'; const claimAbility = new Ability<'read' | 'update'>(); // 第 1 个泛型是字符串,因此该方法只接受一个参数 claimAbility.can('read'); const ability = new Ability<['read' | 'update', 'Article']>(); // 第 1 个泛型是元组,因此该方法接受 2~3 个参数 ability.can('read', 'Article');从 Ability.ts 源码 看,can的核心逻辑是:调用relevantRuleFor(action, subject, field)找到匹配的规则,返回!!rule && !rule.inverted,即“存在规则且该规则不是否定规则”才为true。这保证了cannot规则可以否决can规则,同时允许多条规则按优先级合并后取最先匹配者。
cannot:权限反向判断
cannot(...)与can行为完全一致,只是返回取反的结果。其源码实现(Ability.ts)就是return !this.can(...)。
relevantRuleFor:命中规则的调试查询
relevantRuleFor(...)返回与给定 action、subject 和 field 匹配的那一条规则;找不到时返回null,可用于调试。
- 参数:与
can完全相同; - 返回值:
Rule<Abilities, Conditions> | null。
其实现(Ability.ts)先通过detectSubjectType得到 subject 类型,再调用rulesFor取出候选规则列表,按规则优先级顺序逐一调用matchesConditions(subject)进行条件匹配,返回第一个匹配的规则。相关调试技巧见 调试与测试指南。
rulesFor:查询指定类型的全部规则
rulesFor(...)返回给定 action、subject 类型和 field 下注册的所有规则,适合调试和扩展。与relevantRuleFor不同,它的第二个参数接收的是subject 类型(字符串或类)而非 subject 实例:
- 参数:
action: string;subjectType: SubjectType(当Abilities是字符串字面量类型时可省略);field?: string;
- 返回值:
Rule<Abilities, Conditions>[]。
其实现(RuleIndex.ts)先调用possibleRulesFor取全部候选规则,再在存在字段级规则时用matchesField(field)做字段过滤。注意第三个参数必须是字符串,否则会抛出明确错误。
possibleRulesFor:忽略字段限制的规则查询
possibleRulesFor(...)与rulesFor类似,但最多接受 2 个参数(取决于第一个泛型参数的类型),返回忽略字段级限制的所有可能规则,适合调试和扩展:
- 参数:
action: string;subjectType: SubjectType; - 返回值:
Rule<Abilities, Conditions>[]。
在 RuleIndex.ts 的实现中,它会合并三部分规则:该 subject 类型下的 action 规则、该 subject 类型下的通配 action(manage)规则,以及all通配 subject 下的规则;合并结果会按优先级排序并被Object.freeze缓存,避免重复计算。规则优先级由注册顺序决定——从 RuleIndex.ts 可以看到priority = rawRules.length - i - 1,即规则数组中越靠后的规则优先级越高,这正是“先声明宽泛规则、后声明具体规则”能生效的底层原因。
on:订阅实例更新事件
on(event, handler)允许注册事件处理器。目前仅支持两个事件:
update:在实例被更新之前触发;updated:在实例被更新之后触发。参数:
event: 'update' | 'updated';handler: (event: Event) => void;返回值:一个用于移除事件处理器的函数(
Unsubscribe)。
该能力对前端框架集成非常有用——通常应用里只有一个Ability实例,而很多组件在规则变化后需要重新检查权限。事件底层使用链表结构存储处理器,见 RuleIndex.ts,并且在派发前先把处理器收集进数组,避免处理器在回调中自我退订导致的问题。UpdateEvent会携带rules(新规则数组)、ability(实例)与target(实例)字段。
rules 属性
ability.rules返回传入Ability构造函数的RawRule[]数组(即原始规则,未做索引化处理)。它在 RuleIndex.ts 中定义为一个 getter。
detectSubjectType 方法
实例上的detectSubjectType(subject)方法用于检测对象的 subject 类型,对 subject 类型本身(字符串/类)和 subject 实例都有效:
- 参数:
subject: Subject; - 返回值:
string。
MongoAbility 与 createMongoAbility
原文档中第二个Ability小节实际描述的是MongoAbility:它继承自Ability,并为两个选项设置了默认值:
conditionsMatcher默认设为mongoQueryMatcher;fieldMatcher默认设为fieldPatternMatcher。
同时它把Conditions泛型参数约束为MongoQuery,默认即Ability<Abilities, MongoQuery>。
createMongoAbility(rules?, options?)是一个工厂函数,用于创建“带 Mongo 风格条件”的Ability实例。从 packages/casl-ability/src/createMongoAbility.ts 的实现可以看到,它本质上就是new Ability(rules, { conditionsMatcher: mongoQueryMatcher, fieldMatcher: fieldPatternMatcher, ...options }):
import { createMongoAbility } from '@casl/ability'; const ability = createMongoAbility([ { action: 'read', subject: 'Article', conditions: { private: false } }, ]);这意味着在多数业务项目中,你并不需要直接new Ability(...),而是通过createMongoAbility获得开箱即用的 Mongo 查询条件匹配能力,例如配合 casl-mongoose 或 casl-prisma 将权限直接翻译成数据库查询。
AbilityBuilder:声明式规则构建
AbilityBuilder<TAbility>(实现见 packages/casl-ability/src/AbilityBuilder.ts)允许用声明式的方式构造Ability实例。它只接受一个泛型参数T extends AnyAbility。通常无需显式传入——只要在构造函数中传入Ability的类,TypeScript 会自动推断。
AbilityBuilder 构造函数
- 参数:
AbilityType: AbilityClass<TAbility>(既可以是Ability的类,也可以是createMongoAbility这样的工厂函数,源码通过isAbilityClass区分后分别走new或函数调用,见 AbilityBuilder.ts)。
import { AbilityBuilder, createMongoAbility } from '@casl/ability'; const { can, build } = new AbilityBuilder(createMongoAbility);规则定义见 定义规则指南。
AbilityBuilder 的 can 方法
can(...)在rules数组中注册一条RawRule。根据Ability的Abilities泛型,它接受 1 个参数(字符串 action)或 3 个参数(action + subject + 条件/字段),总体上接受 1~4 个参数,有两个重载:
can(action, subjectType, fields, conditions)can(action, subjectType, conditions)
其中action: string | string[]、subjectType: string | Function、fields: string[]、conditions: Conditions。
返回值:RuleBuilder——一个允许进一步修改已构造RawRule的类,例如通过because(reason)为否定规则添加“被禁止的原因”说明(见 AbilityBuilder.ts)。
从 AbilityBuilder.ts 的_addRule实现可以看到参数分派规则:当第三、四个参数存在且第三个是字符串/数组时,被解释为fields,第四个参数作为conditions;否则第三个参数作为conditions。示例:
import { AbilityBuilder, Ability, createMongoAbility, AbilityClass } from '@casl/ability'; // 仅 action 的 Ability 类型 type ClaimAbility = Ability<'read' | 'update'>; const ClaimAbility = Ability as AbilityClass<ClaimAbility>; const { can, build } = new AbilityBuilder(ClaimAbility); can('read'); can('update'); // 或者 action + subject 的 Ability 类型 const { can, build } = new AbilityBuilder(createMongoAbility); can('read', 'Article', { private: true }); can('read', 'User', ['firstName', 'lastName']); const ability = build();AbilityBuilder 的 cannot 方法
cannot(...)在AbilityBuilder内注册一条inverted(否定)规则,接受与can相同的参数、具有相同的行为,区别是内部为规则打上inverted: true标记。
build 方法
build(options?)构建一个指定Ability类型的实例:
- 参数:
options?: AbilityOptionsOf<TAbility>; - 返回值:一个新的
TAbility实例。
AbilityBuilder 的 rules 属性
builder.rules保存由can和cannot注册的RawRule[]数组(AbilityBuilder.ts),在build()时会原样传给Ability构造函数。
defineAbility:紧凑的函数式定义
defineAbility(define, options?)允许以紧凑的函数形式定义一个MongoAbility实例。它不能用来创建普通Ability实例,非常适用于编写测试与文档。
- 签名(
T为TAbility):<T extends AnyAbility>(define: DSL<T, void>, options?: AbilityOptionsOf<T>) => T<T extends AnyAbility>(define: DSL<T, Promise<void>>, options?: AbilityOptionsOf<T>) => Promise<T>
从 AbilityBuilder.ts 的实现可见,它内部创建了一个基于createMongoAbility的AbilityBuilder,把builder.can与builder.cannot作为回调参数传入define,若define返回 Promise 则等待其完成后调用build(options)。这使defineAbility天然支持异步规则初始化:
import { defineAbility } from '@casl/ability'; const ability = defineAbility((can, cannot) => { can('read', 'Article', { private: false }); cannot('delete', 'Article', { authorId: 1 }); });ForbiddenError:权限不足时中断执行
ForbiddenError<TAbility>(实现见 packages/casl-ability/src/ForbiddenError.ts)用于在用户没有权限时停止后续代码执行。它的构造函数是私有的,因此不能用new实例化(在 TypeScript 中)。它是一个接受TAbility泛型的错误类,同时暴露action、subject、field、subjectType等只读字段,便于在错误处理中输出诊断信息。
import { ForbiddenError, defineAbility } from '@casl/ability'; const ability = defineAbility((can) => { can('read', 'User') }); ForbiddenError.from(ability).throwUnlessCan('read', 'Article'); // fetch article from database // 上述代码若用户无权 read Article,会在此处抛出 ForbiddenError,后续代码不会执行static from
ForbiddenError.from(ability)从给定的Ability实例创建ForbiddenError:
- 参数:
ability: TAbility; - 返回值:
ForbiddenError实例。
static setDefaultMessage
ForbiddenError.setDefaultMessage(messageOrFn)修改所有ForbiddenError的默认错误消息。默认消息为Cannot execute "${error.action}" on "${error.subjectType}":
- 参数:
messageOrFn: string | GetErrorMessage,可以是字符串或返回字符串的函数(error => string); - 返回值:
void。
import { ForbiddenError } from '@casl/ability'; ForbiddenError.setDefaultMessage('Not authorized'); // 或者更详细的函数形式 ForbiddenError.setDefaultMessage(error => `You are not allowed to ${error.action} on ${error.subjectType}`);从 ForbiddenError.ts 的实现可以看到,字符串会被包装成返回该字符串的函数存入静态字段_defaultErrorMessage。
setMessage
setMessage(message)修改某一个ForbiddenError实例的消息:
- 参数:
message: string; - 返回值:
this(链式调用)。
import { ForbiddenError } from '@casl/ability'; import ability from './appAbility'; ForbiddenError.from(ability) .setMessage('You cannot update posts') .throwUnlessCan('update', 'Post');throwUnlessCan
throwUnlessCan(...)接受与Ability.can相同的参数,若用户无法对给定 subject 执行给定 action,则抛出ForbiddenError。其内部先调用unlessCan进行判定(ForbiddenError.ts):若找到正向规则则直接返回;否则填充action、subject、subjectType、field字段,并按“自定义 message → 规则的reason→ 全局默认消息”的优先级确定错误文本(this.message || reason || _defaultErrorMessage(this))。这意味着通过AbilityBuilder的because(reason)设置的规则原因会优先于全局默认消息呈现给用户。
getDefaultErrorMessage
getDefaultErrorMessage()返回ForbiddenError的默认错误消息。在调用ForbiddenError.setDefaultMessage之后,可以用它恢复默认消息(源码中默认消息函数保存在ForbiddenError._defaultErrorMessage)。
fieldPatternMatcher:通配符字段匹配器
fieldPatternMatcher(fields)是一个工厂函数:接受字段数组,返回一个使用通配符(*)按模式匹配字段的函数。它是MongoAbility的默认fieldMatcher选项。
- 工厂参数:
fields: string[]; - 工厂返回值:
MatchField; - 匹配器参数:
field: string; - 匹配器返回值:
boolean。
import { fieldPatternMatcher } from '@casl/ability'; const matchField = fieldPatternMatcher(['name', 'email', 'address.**']); console.log(matchField('name')); // true console.log(matchField('address.street')); // true从 packages/casl-ability/src/matchers/field.ts 的源码实现可以看到:*匹配单段字段名(不跨.),**匹配跨层级的任意路径;当字段数组不含任何*时,会退化为精确的indexOf查找,避免不必要的正则编译开销(惰性求值:首次调用才编译正则并缓存)。
相关主题:Ability API、限制字段访问、自定义 Ability。
mongoQueryMatcher:MongoDB 查询条件匹配器
mongoQueryMatcher(conditions)是一个工厂函数,基于 MongoDB 查询语言 创建匹配 subject 的函数。它是MongoAbility的默认conditionsMatcher选项。
- 工厂参数:
conditions: MongoQuery; - 工厂返回值:
ConditionsMatcher<MongoQuery>; - 匹配器参数:
object: Record<PropertyKey, any>; - 匹配器返回值:
boolean。
import { mongoQueryMatcher } from '@casl/ability'; const matchConditions = mongoQueryMatcher({ authorId: 1, private: true }); console.log(matchConditions({ authorId: 2 })); // false console.log(matchConditions({ authorId: 1, private: true })); // true从 packages/casl-ability/src/matchers/conditions.ts 的源码可以看到,它基于@ucast/mongo2js实现,内置了$eq、$ne、$lt、$lte、$gt、$gte、$in、$nin、$all、$size、$regex、$options、$elemMatch、$exists等字段级操作符,以及eq、ne、within、and等解释器,还支持and逻辑组合。该匹配器可作为conditionsMatcher选项传给Ability类。
相关主题:Ability API、条件深入、自定义 Ability。
buildMongoQueryMatcher:扩展 Mongo 操作符
buildMongoQueryMatcher(parsingInstructions, interpreters)是一个“工厂的工厂”:它允许用自定义 Mongo 操作符扩展mongoQueryMatcher。
- 工厂参数:
parsingInstructions: Record<string, ParsingInstruction>;interpreters: Record<string, JsOperator>; - 工厂返回值:扩展后的
mongoQueryMatcher。
源码实现(conditions.ts)就是把自定义的解析指令和解释器分别合并到默认集合{ ...defaultInstructions, ...instructions }与{ ...defaultInterpreters, ...interpreters }后再创建工厂。结果同样可以作为conditionsMatcher选项传给Ability类。
相关主题:mongoQueryMatcher API、自定义 Ability。
createAliasResolver:action 别名解析器
createAliasResolver(aliasMap)创建一个把别名解析为真实 action 的函数,可作为resolveAction选项传给Ability类。
- 参数:
aliasMap: AliasMap; - 返回值:
(action: string | string[]) => string | string[]。
从 packages/casl-ability/src/utils.ts 的实现看,createAliasResolver在创建时默认会校验别名表:不允许把保留 actionmanage用作别名、不允许出现循环别名(否则抛出明确错误),随后返回一个把别名逐层展开合并为真实 action 数组的函数。例如可以定义readable别名指向['read', 'list'],之后规则里写readable时会被自动展开。详见 定义 action 别名。
detectSubjectType:默认 subject 类型检测
detectSubjectType(subject)是默认的 subject 类型检测逻辑,可作为detectSubjectType选项传给Ability类,也可在其基础上扩展。检测顺序如下:
- 若
subject为undefined,返回all(对应RuleIndex中的anySubjectType默认值,见 RuleIndex.ts); - 若
subject是ForcedSubject(通过subject()强制标记过类型),返回其强制类型; - 若
subject是对象,返回constructor.modelName || constructor.name。
- 参数:
subject?: {}; - 返回值:字符串(subject 类型)。
从 utils.ts 的源码实现可以看到,modelName优先于name,这为 Mongoose 等 ORM 的模型类型检测提供了挂载点;且仅当对象自身(而非原型链)带有__caslSubjectType__标记时才会返回该标记值。另外,RuleIndex.ts 会在建索引时根据 subject 类型是字符串还是类来自动优化检测策略(DETECT_SUBJECT_TYPE_STRATEGY),无需每次调用都走通用检测路径。
相关主题:subject 类型检测指南。
subject:为普通对象强制标记 subject 类型
subject(subjectType, object)为普通对象设置 subject 类型。如果你使用类或自定义了detectSubjectType算法,则不需要使用它。它接受两个泛型参数TSubjectType和TObject:
- 参数:
subjectType: TSubjectType;object: TObject; - 返回值:
TObject & ForcesSubject<TSubjectType>(带有__caslSubjectType__标记的交叉类型)。
底层实现是 utils.ts 中的setSubjectType(通过入口导出为subject):用Object.defineProperty定义不可枚举的__caslSubjectType__属性;如果对象已被标记为其他类型再重新标记,会抛出错误以避免类型冲突。
import { subject } from '@casl/ability'; const post = subject('Post', { id: 1, title: 'Hello' }); // 之后 can('read', post) 会按 'Post' 类型进行规则匹配相关主题:subject 类型检测指南。
与周边生态的衔接
core 包的这些 API 是整个 CASL 体系的地基:casl-mongoose(packages/casl-mongoose)的accessibleBy将MongoAbility的规则翻译成 Mongoose 查询;casl-prisma(packages/casl-prisma)通过createPrismaAbility把条件转译为 Prisma 查询;casl-react(packages/casl-react)与casl-vue(packages/casl-vue)则依赖Ability.update与on('updated')事件实现响应式的权限 UI 更新。理解本文的 core API 是深入使用这些框架集成包的前提。
- 认证鉴权
【免费下载链接】casl
CASL is an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access
相关推荐
CASL 的 @casl/ability/extra API 实战指南:rulesToQuery、rulesToAST、rulesToFields、permittedFieldsOf 与规则打包
CASL 的 @casl/ability/extra API 实战指南:rulesToQuery、rulesToAST、rulesToFields、permit
认证鉴权最强权限控制:CASL @casl/ability 2025实战全解析
最强权限控制:CASL @casl/ability 2025实战全解析 还在手动编写复杂的权限验证逻辑?每次新增功能都要重新设计权限系统?一文解决你所有权限管理
认证鉴权CASL 入门指南:使用 @casl/ability 实现同构 JavaScript 权限控制
CASL 入门指南:使用 @casl/ability 实现同构 JavaScript 权限控制 CASL(读作 /ˈkæsəl/,如 castle )是一个同构
认证鉴权
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考