基于属性的访问控制如何助你编写更优授权规则

TL;DR · AI 摘要
ABAC通过动态评估用户、资源和环境属性解决RBAC角色爆炸问题,是构建细粒度授权规则的更优解。文章系统梳理了从DAC到ABAC的演进逻辑,并提供了策略结构与代码实现指南。
核心要点
- RBAC在权限细化时会导致角色数量指数级增长(Role Explosion),ABAC通过属性组合替代固定角色绑定。
- ABAC决策基于四类属性:Subject(用户)、Resource(资源)、Action(操作)和Environment(上下文环境)。
- NIST SP 800-162标准定义了ABAC架构,建议当业务规则涉及时间、地点或数据敏感度等动态条件时优先采用ABAC。
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- ABAC授权模型
- 演进背景
- DAC/MAC局限性
- ACL管理负担
- RBAC角色爆炸
- 核心机制
- 动态属性评估
- Subject/Resource/Env
- 策略与代码解耦
- 适用场景
- 细粒度数据隔离
- 上下文敏感访问
金句 / Highlights
值得收藏与分享的关键句。
随着权限要求变得更细粒度,你必须创建更具体的角色……这种泛滥被称为“角色爆炸”。
ABAC将关注点从“用户拥有什么角色”转移到“我们对用户、资源和情境了解什么”。
未能持续更新ACL会导致用户积累他们本不应再拥有的权限。
护士获得访问权不是因为拥有“护士”角色,而是因为其职位是“执业护士”且结合了上下文条件。
标题:基于属性的访问控制如何助你编写更优的授权规则
URL 来源:https://www.freecodecamp.org/news/how-attribute-based-access-control-helps-you-write-better-authorization-rules/
发布时间:2026-06-04T17:22:22.069Z
Markdown 内容:

任何处理用户数据的应用程序最终都会遇到同一个问题:并非所有用户都应该看到相同的内容。
初级护士不应有权访问医院的所有患者记录。外包人员不应能查阅内部财务报告。如果一名员工在凌晨两点从未识别的设备登录,他可能不应该编辑生产环境的配置文件。
简单的基于角色的系统能够很好地处理显而易见的场景。但随着应用程序规模扩大、访问规则日益复杂,这些系统便开始显露弊端。你不得不创建越来越多特定的角色,例如 finance_viewer(财务查看者)、finance_viewer_us_only(仅限美国区财务查看者)、finance_viewer_us_only_readonly(仅限美国区财务只读查看者),直到角色本身变得难以管理。
基于属性的访问控制(ABAC)正是为解决这一问题而设计的。它将关注点从“该用户拥有什么角色?”转变为“关于该用户、该资源以及当前环境,我们掌握了哪些信息?”,并综合所有这些因素来做出访问决策。
在本指南中,你将了解 ABAC 的工作原理、它如何从早期的访问控制模型演变而来、策略的结构方式、如何在代码中实现它,以及何时应该使用它。
目录
先决条件
为了充分理解本文内容,建议你具备以下条件:
- 对 Web 身份验证(登录、会话、Token)有基本了解
- 熟悉应用程序中用户与资源之间的关系
- 具备一定的 JavaScript 或伪代码阅读经验
无需预先掌握访问控制理论知识。
访问控制的演进历程
要理解 ABAC 为何诞生,有必要先了解其前身,以及每一代模型为何无法满足需求。
自主访问控制与强制访问控制
早期的访问控制模型源于 20 世纪 60 年代和 70 年代的国防部应用。根据 NIST 特别出版物 800-162,这些模型包括自主访问控制(DAC)和强制访问控制(MAC)。
在 DAC 中,资源的所有者决定谁可以访问该资源。这就像你电脑上的文件,由你选择谁能读取或编辑它。而在 MAC 中,访问权限由中央机构通过“机密”或“绝密”等标签进行管理。系统会强制执行这些标签,而非由个人所有者决定。
这两种模型在其最初的应用场景中行之有效,但难以扩展以适应现代网络系统的复杂性。
基于身份的访问控制与访问控制列表
随着网络规模的扩大,基于身份的访问控制(IBAC)变得普遍起来。最常见的实现方式是访问控制列表(ACL),即附加在资源上的用户或组列表,用于指定每个主体可执行的操作。
ACL 简单直观,但随着系统规模增长,会带来沉重的管理负担。每新增一个用户,都需要将其添加到所有相关列表中。每次权限变更都意味着要在多个资源的列表中逐一排查。而当有人离开组织时,你需要在所有地方找到并移除其权限。
若不能始终如一地执行这些操作,就会导致用户累积本不该拥有的权限。
基于角色的访问控制
基于角色的访问控制(RBAC)是一次重大飞跃。RBAC 不再直接将权限分配给用户,而是将权限分配给角色,再将角色分配给用户。例如,一家医院可能设有 nurse(护士)、doctor(医生)、admin(管理员)和 billing_staff(计费员)等角色,每个角色拥有不同的权限。
这大大简化了管理工作。新员工入职只需为其分配合适的角色;员工离职只需移除其角色;若要调整护士的权限,只需更新一次护士角色即可。
RBAC 得到了广泛采用,至今仍是许多应用的正确选择。但它存在一个结构性弱点:随着权限需求变得更加细粒度,你必须创建更多特定的角色。例如,一名护士如果只能查看所在楼层的患者、仅能在值班期间访问、或仅限于特定类型的记录,就需要一个非常具体的角色,或者一组以复杂方式交互的角色组合。
这种泛滥现象被称为“角色爆炸”。角色数量不断激增,直到它们变得和 RBAC 原本旨在取代的独立权限一样难以管理。
基于属性的访问控制
ABAC 的出现正是为了应对角色爆炸问题。ABAC 不再分配捆绑固定权限的角色,而是在每次访问请求发生时,评估用户、资源和上下文的实际特征。
护士之所以能访问某份患者记录,并非因为其拥有 nurse 角色,而是因为其职称为“执业护士”、患者位于其负责的楼层、当前处于其值班时段,且该记录类型在其护理范围内。只要上述任一事实发生变化,访问决策也会随之改变。
正如 NIST SP 800-162 所定义的,ABAC 是:
“一种访问控制方法,其中主体对客体执行操作的请求是否被允许,取决于主体的已分配属性、客体的已分配属性、环境条件,以及一组根据这些属性和条件指定的策略。”
ABAC 是一种逻辑访问控制模型,每个访问决策都是通过将一组规则与属性的当前值进行评估后做出的。没有任何内容是预先计算或缓存在角色分配中的。每当用户尝试执行某项操作时,系统都会询问:鉴于我们对该用户、该资源及当前时刻的了解,是否应允许此操作?
这使得 ABAC 具有极高的精确性和动态性。权限不会随时间累积,也不需要在人员角色变更时进行手动清理。系统只需在每次访问时评估属性的当前状态即可。
NIST 的 ABAC 指南正式指出,该模型能够同时实施自主访问控制(DAC)和强制访问控制(MAC)概念,因此比仅支持其中一种的模型具有更强的表达能力。
Axiomatics 等公司、主要政府机构以及管理跨组织数据共享的大型企业都依赖 ABAC,因为它能够在复杂环境中有效扩展安全策略。
ABAC 的四大构建模块
每个 ABAC 系统都由四类信息构建而成。清晰理解这些要素是掌握 ABAC 工作原理的关键。
1. 主体属性
主体是指发起访问请求的人或实体。通常是用户,但也可能是服务、应用程序或自动化系统,即 NIST 所称的非人实体(NPE)。
主体属性描述了主体的身份特征:
user.jobTitle = "Nurse Practitioner"
user.department = "Cardiology"
user.clearanceLevel = "Confidential"
user.employmentStatus = "Active"
user.location = "Floor 3"
user.shiftActive = true这些属性通常来源于身份提供商、HR 系统或用户目录。它们是关于用户的事实信息,可直接用于策略判定。
2. 客体属性(资源属性)
客体是指主体试图访问的目标。它可以是文件、数据库记录、API 端点、服务或任何其他受保护的资源。
客体属性描述了资源的特征:
record.type = "PatientMedical"
record.floor = "Floor 3"
record.sensitivity = "High"
record.owner = "Dr. Williams"
record.department = "Cardiology"客体属性通常在资源创建时分配,并在其整个生命周期中不断更新。它们是决定谁有权访问该资源的依据。
3. 操作属性
操作是指主体试图对客体执行的动作。常见操作包括读取、写入、编辑、删除、复制、执行和共享。
在许多 ABAC 实现中,操作本身也具有属性:
action.type = "read"
action.bulk = false策略可以独立于其他属性来限制允许的操作。即使用户的所有其他属性都匹配,也可能只被允许读取文档而不能删除它。
4. 环境条件
环境条件是不属于主体或客体,但会影响访问决策的上下文因素。NIST 将其描述为“独立于主体和客体的动态因素,可在决策时作为属性使用以影响访问决策”。
示例如下:
environment.time = "14:30"
environment.dayOfWeek = "Wednesday"
environment.userLocation = "Corporate Office"
environment.ipAddress = "192.168.1.10"
environment.deviceStatus = "compliant"
environment.threatLevel = "low"环境条件使 ABAC 真正具备了动态特性。相同的用户、资源和操作,在工作时间通过可信设备访问可能被允许,但在午夜从未知 IP 地址发起请求则可能被拒绝。
ABAC 决策流程
当主体试图对客体执行操作时,ABAC 系统会执行以下特定流程:
第一步:收集属性
系统收集主体、客体、操作和环境的当前属性。这可能涉及查询用户目录、读取资源元数据以及检查当前时间和位置。
第二步:查找适用策略
系统识别适用于当前请求的策略。例如,读取患者记录的请求可能触发多个策略:一个关于临床人员访问权限,一个关于非工作时间访问,还有一个关于记录敏感级别。
第三步:评估各项策略
每个适用的策略都会根据收集到的属性进行评估,并返回“允许”或“拒绝”的结果。
第四步:解决冲突
如果多个适用策略之间存在冲突,系统将使用预定义的组合规则。常见的方法包括“拒绝优先”(只要任一策略拒绝,请求即被拒绝)或“允许优先”(只要任一策略允许,请求即被批准)。
第五步:执行决策
系统根据最终决策授予或拒绝访问权限。
每次发起访问请求时都会执行此流程。系统不会缓存角色分配或预计算权限表。决策完全反映请求时刻所有属性的实时状态。
如何编写 ABAC 策略
策略是 ABAC 的核心逻辑。它们以引用属性的条件规则形式编写。优秀的策略读起来就像业务规则,因为其本质正是如此。
简单布尔策略
最基本的形式是评估特定属性是否匹配:
// Policy: Only active employees can access internal resources
function canAccessInternalResource(user) {
return user.employmentStatus === "Active";
}功能说明: 在允许访问前检查单一属性——雇佣状态。任何处于非活跃、停职或离职状态的用户都会被拒绝,无论其角色或过往访问历史如何。
多属性策略
实际策略通常会组合多个属性:
// Policy: A nurse can read a patient record
// if the patient is on their assigned floor
// and during their active shift
function canReadPatientRecord(user, record, environment) {
const isNurse = user.jobTitle === "Nurse Practitioner";
const isAssignedFloor = user.assignedFloor === record.floor;
const isActiveDuty = user.shiftActive === true;return isNurse && isAssignedFloor && isActiveDuty; }
**功能说明:** 使用 AND 逻辑组合了三个条件。只有当这三个条件都为真时,才会授予访问权限。一旦更改护士的楼层分配,他们将立即失去对原楼层记录的访问权限,无需任何人工干预。
### 环境感知策略
添加环境条件可使策略具备上下文感知能力:
// 策略:用户只能在工作时间通过公司网络访问敏感财务记录
function canAccessSensitiveFinancialRecord(user, record, environment) { const isFinanceStaff = user.department === "Finance"; const isHighSensitivity = record.sensitivity === "High";
// 如果是高敏感度记录,则应用时间和位置控制 if (isHighSensitivity) { const currentHour = new Date(environment.timestamp).getHours(); const isBusinessHours = currentHour >= 9 && currentHour < 17; const isCorporateNetwork = environment.ipRange === "corporate";
return isFinanceStaff && isBusinessHours && isCorporateNetwork; }
// 低敏感度记录仅要求用户属于财务部门 return isFinanceStaff; }
**功能说明:** 对敏感度较高的资源实施更严格的控制。同一用户可以随时访问低敏感度记录,但访问高敏感度记录时必须在工作时间内且位于公司网络中。该策略逻辑反映了实际业务规则:敏感数据需要更高级别的保护。
### 基于所有权的策略
ABAC 还可以实现自主式所有权规则:
// 策略:如果用户是文档所有者,或者拥有编辑权限 // 且文档未被锁定,则可以编辑该文档
function canEditDocument(user, document, action) { const isOwner = document.ownerId === user.id; const hasEditorPermission = user.permissions.includes("document.edit"); const isUnlocked = document.status !== "locked";
return (isOwner || hasEditorPermission) && isUnlocked; }
**功能说明:** 将所有权(用户与文档之间关系的属性)与显式权限及资源状态相结合。即使编辑者拥有编辑权限,也无法编辑已锁定的文档。所有者可以编辑自己的文档,但同样不能编辑已锁定的文档。
## 如何在代码中实现 ABAC
让我们构建一个简单的 ABAC 评估引擎,将上述各部分整合起来。
### 第一步:定义属性结构
首先,为属性定义清晰的数据结构:
// 请求访问的用户(主体) const user = { id: "user-123", name: "Sarah Chen", department: "Cardiology", jobTitle: "Nurse Practitioner", clearanceLevel: 2, assignedFloor: "Floor 3", shiftActive: true, employmentStatus: "Active" };
// 被访问的资源(客体) const patientRecord = { id: "record-456", type: "PatientMedical", floor: "Floor 3", sensitivity: 2, ownerId: "doctor-789", department: "Cardiology" };
// 环境条件 const environment = { timestamp: new Date().toISOString(), ipAddress: "10.0.1.25", ipRange: "corporate", deviceCompliant: true };
### 第二步:编写策略函数
将各个策略编写为纯函数,接收属性作为参数并返回布尔值:
// policies/patientRecord.js
// 策略 1:用户必须处于在职状态且为临床工作人员 function isClinicalStaff(user) { const clinicalTitles = [ "Nurse Practitioner", "Physician", "Resident", "Medical Assistant" ]; return ( user.employmentStatus === "Active" && clinicalTitles.includes(user.jobTitle) ); }
// 策略 2:记录必须在用户的分配区域内 function isAssignedToRecord(user, record) { return ( user.department === record.department && user.assignedFloor === record.floor ); }
// 策略 3:用户必须处于值班状态 function isOnActiveShift(user) { return user.shiftActive === true; }
// 策略 4:访问高敏感度记录需要使用合规设备 function meetsDeviceRequirements(record, environment) { if (record.sensitivity >= 3) { return environment.deviceCompliant === true; } return true; // 低敏感度记录无设备要求 }
**功能说明:** 每个策略都是一个小型、专注的函数。这使得策略易于单独测试、易于阅读,并且便于在不同的访问决策中复用。例如,“该用户是否为临床工作人员”这一策略可应用于多种不同的资源类型。
### 第三步:构建评估引擎
将策略组合到一个决策引擎中:
// abac/engine.js
function evaluateAccess(user, resource, action, environment, policies) { // 收集所有策略的评估结果 const results = policies.map(policy => { try { return policy(user, resource, action, environment); } catch (error) { console.error(Policy evaluation error: ${error.message}); return false; // 失败关闭:出错时拒绝访问 } });
// 拒绝优先:只要有任何一个策略拒绝,即拒绝访问 return results.every(result => result === true); }
// 组装读取患者记录所需的策略 const readPatientRecordPolicies = [ (user) => isClinicalStaff(user), (user, record) => isAssignedToRecord(user, record), (user) => isOnActiveShift(user), (user, record, action, environment) => meetsDeviceRequirements(record, environment) ];
// 做出访问决策 const canRead = evaluateAccess( user, patientRecord, "read", environment, readPatientRecordPolicies );
console.log(Access ${canRead ? "granted" : "denied"}); // → Access granted(所有条件均满足)
**功能说明:** 引擎会遍历每个策略函数,并传入相关属性。如果所有策略均返回 true,则授予访问权限;只要有任何一个策略返回 false,则拒绝访问。这种机制称为“拒绝优先(deny-overrides)”组合算法。`try-catch` 块确保了当策略执行抛出错误时,系统会拒绝而非授予访问权限,这遵循了“故障关闭(fail-closed)”的安全原则。
### 第 4 步:添加属性收集器
在实际应用中,属性通常来自多个数据源:
// attributes/collector.js
async function collectAttributes(userId, resourceId) { // 并行收集以提升性能 const [user, resource, environment] = await Promise.all([ fetchUserAttributes(userId), // 来自身份提供商或 HR 系统 fetchResourceAttributes(resourceId), // 来自资源元数据存储 collectEnvironmentConditions() // 时间、IP、设备状态 ]);
return { user, resource, environment }; }
async function fetchUserAttributes(userId) { // 此处查询用户目录、LDAP 或身份提供商 const user = await userDirectory.findById(userId); const shift = await shiftService.getActiveShift(userId);
return { ...user, shiftActive: shift !== null, assignedFloor: shift?.floor || null }; }
async function collectEnvironmentConditions() { return { timestamp: new Date().toISOString(), ipAddress: request.ip, ipRange: await networkService.classifyIP(request.ip), deviceCompliant: await deviceService.checkCompliance(request.deviceId) }; }
**功能说明:** 属性收集与策略评估被分离开来。这是一个重要的设计决策:这意味着你可以在不依赖真实用户或资源的情况下,使用任意属性值来测试策略。同时,这也意味着你可以随时更换属性来源(例如从本地目录迁移到云端身份提供商),而无需修改任何策略代码。
### 第 5 步:集成到 API 中
在你的 API 处理程序中使用评估引擎:
// middleware/abac.js
function requireAccess(action, resourceType) { return async (req, res, next) => { try { const { user, resource, environment } = await collectAttributes( req.user.id, req.params.id );
const policies = getPoliciesFor(resourceType, action); const allowed = evaluateAccess(user, resource, action, environment, policies);
if (!allowed) { // 记录拒绝日志以供审计 auditLog.record({ userId: req.user.id, resourceId: req.params.id, action, decision: "denied", timestamp: new Date() });
return res.status(403).json({ error: "Access denied" }); }
next(); } catch (error) { // 故障关闭:发生意外错误时拒绝访问 return res.status(403).json({ error: "Access denied" }); } }; }
// 在路由定义中使用 app.get( "/patient-records/:id", authenticate(), // 首先验证身份 requireAccess("read", "patientRecord"), // 然后进行 ABAC 评估 patientRecordController.getById // 最后处理请求 );
**功能说明:** ABAC 检查位于中间件中,在身份认证之后、路由处理器之前执行。身份认证确认“用户是谁”,而 ABAC 决定“该用户是否可以执行其尝试的操作”。这种分离使授权逻辑与业务逻辑解耦。
## ABAC 与 RBAC:如何选择
RBAC 并未过时,对于许多应用来说它确实是正确的选择。关键在于哪种模型更适合你特定的访问控制需求。
### RBAC 的优势
RBAC 易于理解、易于实现且易于审计。如果你能将访问需求描述为一系列具有固定权限的角色列表,那么 RBAC 就能很好地工作。大多数 SaaS 应用都以 RBAC 起步,并且在多年内都能满足需求。
典型的 RBAC 检查如下所示:
// 简单的 RBAC:用户是否拥有所需角色? function canAccess(user, requiredRole) { return user.roles.includes(requiredRole); }
这种方式快速、清晰且易于调试。当出现问题时,只需检查用户拥有哪些角色以及资源需要哪些角色即可。
### RBAC 的局限性
当权限需要依赖于角色无法涵盖的因素时,RBAC 就会显得力不从心。例如,若你需要表达“财务经理可以查看财务记录,但仅限其负责的区域,且仅在工作时间内”,这就超出了单一角色能清晰表达的范畴。
此时,你要么创建一个极其具体的角色(如 `finance_manager_us_east_business_hours`),从而导致“角色爆炸”问题;要么在应用代码中添加条件逻辑,这实际上是以一种更混乱的方式重新实现了 ABAC。
### RBAC 与 ABAC 对比
| 因素 | RBAC | ABAC |
| --- | --- | --- |
| 逻辑 | 权限分配给角色,角色分配给用户 | 策略在决策时动态评估属性 |
| 粒度 | 粗粒度 | 细粒度且感知上下文 |
| 灵活性 | 低,新规则通常需要新角色 | 高,更新策略即可,无需更改角色 |
| 可扩展性 | 复杂度增加时易导致角色爆炸 | 随策略复杂度扩展,而非角色数量 |
| 可审计性 | 简单,检查角色分配即可 | 需要在决策时记录属性日志 |
| 复杂性 | 低 | 较高,涉及更多组件 |
| 适用场景 | 简单、稳定的权限结构 | 复杂、动态或依赖上下文的权限控制 |
### 结合两种模型
RBAC 和 ABAC 可以协同工作。一种常见的模式是:使用 RBAC 进行粗粒度的访问控制(用户可以访问应用的哪些模块?),并在这些模块内部使用 ABAC 进行细粒度控制(用户可以访问哪些具体记录?)。
例如,某个角色可能授予对医院系统中患者病历模块的访问权限。在该模块内,ABAC 策略会根据用户所属科室、分配楼层和当前班次,决定其可以查看或编辑哪些具体病历。
## 实际应用场景
### 医疗记录管理
医疗行业是体现 ABAC 重要性的最典型案例之一。患者隐私法规要求精确的访问控制,而患者护理又要求相关医护人员在需要时能够快速调取病历。
在医院中,ABAC 策略可能规定护士仅在满足以下所有条件时才能查看患者病历:
1. 患者当前入住在该护士负责的楼层,
2. 护士处于当班状态,
3. 访问请求来自医院内部网络,
4. 且该病历类型属于护士的护理职责范围。
根据 WorkOS 的 ABAC 分析,在紧急情况下,ABAC 系统可以自动扩展访问权限。例如,急诊科医生会自动获得更广泛的患者病历访问权以便立即施救,且这种访问权限具有时效性并受到严密监控。
如果在 RBAC 系统中实现上述所有规则,则需要创建数十个角色,而且即便如此,这些角色仍难以动态处理紧急访问场景。
### 企业数据访问
大型企业通常拥有跨部门、跨职位、跨地域和不同安全级别的员工,他们需要对同一底层数据进行差异化访问。例如,某文档可能仅允许美国区域的财务经理在工作时间内访问,允许全球高管随时访问,但完全禁止外包人员访问。
ABAC 可以通过策略表达所有这些规则。当员工调换部门、休假或变更职位时,其在身份系统中的属性会自动更新,访问权限也随之自动调整,无需手动更新 ACL。
### 政府与机密信息
美国联邦政府对 ABAC 的应用在 NIST SP 800-162 中有详细描述,该标准旨在满足联邦身份、凭证和访问管理 (FICAM) 的要求。联邦机构需要处理跨组织边界共享的信息,这些信息具有不同的密级和“按需知悉”要求。
借助 ABAC,一个机构的分析员可以访问另一个机构的信息,而无需后者为其预先创建账户。系统会在请求发生时,根据资源的密级和访问规则,实时评估该分析员的安全许可属性、组织归属和项目分配情况。
### 多租户 SaaS 应用
服务于多个组织的 SaaS 应用既需要确保租户间严格的数据隔离,又要支持每个租户内部复杂的权限结构。
ABAC 能够自然地应对这一需求。通过将资源属性(如 `record.tenantId`)与用户属性(`user.tenantId`)进行比对,策略层面即可杜绝跨租户访问。而在租户内部,ABAC 可以根据租户策略的需求支持任意复杂度的权限控制。
## 企业级 ABAC 实施考量
在企业规模部署 ABAC 会带来一些小型实施中不存在的挑战。
### 策略管理
策略需要经过编写、审查、测试和部署。根据 NIST SP 800-162,这需要一个策略管理点 (PAP),即用于创建和管理策略的接口。如果缺乏合适的工具,策略将变得难以审计和维护。
在实践中,这意味着需要像对待代码一样对待策略:实行版本控制、代码审查和自动化测试。
### 属性质量与时效性
ABAC 的有效性完全取决于其所评估的属性。如果用户属性已过时(例如用户已调岗但目录条目尚未更新),访问决策就会出错。
NIST 警告称:“刷新频率较低的属性,其安全性最终将低于实时刷新的属性。”从权威数据源构建可靠的属性同步管道,往往是 ABAC 部署中最困难的部分。
### 性能
对每个请求都进行策略评估会产生性能开销。每次评估可能需要从多个来源获取属性。为了缓解这一问题,许多实现方案采用了属性缓存,但缓存又会引入上述的数据过时问题。
解决方案是根据各类属性的变化频率设置合理的 TTL(生存时间)。用户的部门信息变动较少,可以缓存数小时;用户的当班状态可能每 8 小时变化一次,需要较短的缓存时间;而实时位置信息可能根本无法缓存。
### 审计日志
由于 ABAC 是动态做出决策的,因此审计不仅需要记录决策结果,还必须记录每次决策所使用的属性。如果日志仅显示“拒绝访问”,而未记录拒绝原因及具体是哪个属性未能满足哪条策略,那么该日志的价值将大打折扣。
NIST 指出,如果不记录决策时的属性值,就无法满足问责制要求。
## 局限性与挑战
ABAC 功能强大,但并非适用于所有访问控制问题。在决定实施之前,有必要客观认识其局限性。
**复杂性**:正如 NIST SP 800-162 所述,“与简单的访问控制系统相比,ABAC 系统更为复杂,因此实施和维护成本也更高。”赋予 ABAC 强大能力的灵活性,同时也增加了理解和排查的难度。当用户询问“为什么我无法访问这个?”时,管理员需要检查所有被评估的属性以及未满足的条件。
**策略冲突**:在包含大量策略的复杂系统中,策略之间可能会发生冲突。两条策略单独看似乎都正确,但组合在一起却可能产生意外结果。解决这些冲突需要明确的优先级规则和审慎的策略设计。
**属性管理开销**:在庞大的用户群体中维护准确的属性,需要对身份基础设施进行投入。来自不同系统的属性需要被标准化、验证并保持同步。正如 NIST 所述,组织需要的不仅仅是一个策略引擎,而是一整套属性管理基础设施。
**测试困难**:由于访问权限取决于可能多达数十个属性的组合,因此全面测试边界情况需要周密考虑。对于典型场景运行正常的策略,在面对不常见的属性组合时可能会出现意外行为。
**并非总是值得投入**:对于访问需求简单的应用,ABAC 会引入不必要的复杂性。如果你的需求可以清晰地表达为一组具有固定权限的角色,那么 RBAC 是更好的选择。
## 结论
基于属性的访问控制代表了应用授权管理方式的一次实质性演进。ABAC 不再维护不断膨胀的角色和权限列表,而是在每次请求发生时评估用户、资源和上下文的实际特征。
它解决了困扰复杂 RBAC 实现的角色爆炸问题。它使访问规则能够反映真实的业务策略,而非仅仅是技术层面的近似模拟。它能够处理动态场景、紧急情况、基于时间的限制以及跨组织访问,而这些往往是静态角色难以甚至无法表达的。
但 ABAC 并非在所有情况下都更优。它的构建更复杂,调试更困难,并且需要投入资源建设属性管理基础设施,而这些都是简单模型所不需要的。许多应用使用 RBAC 即可满足需求,也有一些应用将 RBAC 与 ABAC 结合使用。
正确的问题不是“我应该使用 ABAC 吗?”,而是“我的访问需求是否足够复杂,以至于对 ABAC 的投入能够产生回报?”如果你的访问规则频繁变更、依赖于资源或环境上下文,或者需要跨组织边界扩展,那么 ABAC 值得认真考虑。
首先应识别当前访问控制模型在哪些方面已难以为继。如果你正在为每个边界情况创建角色,如果在路由处理程序中编写检查特定属性值的条件逻辑,或者如果用户积累了本不应再拥有的权限,这些都是信号,表明更具表达力的模型会有所帮助。
当角色不够用时,ABAC 就是那个解决方案。
## 术语表
**ABAC(Attribute-Based Access Control,基于属性的访问控制)**:一种访问控制方法,通过根据主体、客体、操作和环境条件的属性评估策略来做出授权决策。NIST 将其定义为“基于分配的属性来授予或拒绝主体对客体执行操作的请求”的方法。
**Subject(主体)**:请求访问资源的实体。通常是人类用户,但也可以是服务、自动化流程或设备。也称为“请求者”。
**Object(客体)**:受保护的资源,例如文件、数据库记录、API 端点、服务或任何由 ABAC 系统管理访问的系统资源。
**Attribute(属性)**:主体、客体、操作或环境的特征,以键值对形式表示。例如 `user.department = "Finance"` 或 `record.sensitivity = "High"`。
**Subject Attributes(主体属性)**:描述发起请求的用户或服务的属性,如职位、部门、安全许可级别或当前位置。
**Object Attributes(客体属性)**:描述被访问资源的属性,如其类型、所有者、敏感级别或所属部门。
**Environment Conditions(环境条件)**:独立于主体和客体但会影响访问决策的上下文因素。示例包括一天中的时间、星期几、IP 地址、设备合规状态或当前威胁级别。
**Policy(策略)**:一条或一组规则,用于评估属性值以确定是否应允许或拒绝特定的访问请求。ABAC 策略通常以逻辑条件的形式编写。
**Policy Decision Point (PDP,策略决策点)**:ABAC 系统中负责评估策略和属性以计算访问决策的组件。
**Policy Enforcement Point (PEP,策略执行点)**:拦截访问请求并执行 PDP 所做决策的组件。
**Policy Information Point (PIP,策略信息点)**:检索 PDP 做出决策所需属性值的组件。
**Policy Administration Point (PAP,策略管理点)**:提供创建、测试和管理策略接口的组件。
**RBAC(Role-Based Access Control,基于角色的访问控制)**:一种将权限分配给角色、再将用户分配给角色的访问控制模型。比 ABAC 简单,但在应对复杂、动态的访问需求时表达能力较弱。
**Role Explosion(角色爆炸)**:随着访问需求日益细化,RBAC 系统中出现越来越多高度具体的角色,最终导致角色的管理难度与单独管理权限相当。
**DAC(Discretionary Access Control,自主访问控制)**:一种由资源所有者控制谁可以访问其资源的访问控制模型。常见于文件系统。
**MAC(Mandatory Access Control,强制访问控制)**:一种由中央机构使用分类标签来控制访问的访问控制模型,不受资源所有者意愿的影响。
**ACL(Access Control List,访问控制列表)**:与资源关联的列表,指定哪些用户或组拥有哪些权限。常见于基于身份的访问控制系统。
**Non-Person Entity (NPE,非人员实体)**:非人类用户的主体,如自动化服务、应用程序或网络设备,可以请求访问资源。
**Attribute Caching(属性缓存)**:存储先前检索到的属性值以提高性能,但代价是访问决策可能会使用过时的数据。
**拒绝优先合并**:一种策略合并规则,即只要任何适用的策略返回拒绝,则最终决策即为拒绝,无论其他策略是否可能返回允许。
**故障关闭**:一种安全设计原则,指在发生意外错误或信息缺失时,系统默认拒绝访问而非授予访问权限,从而降低未授权访问的风险。
_来源:定义改编自 NIST 特别出版物 800-162,《基于属性的访问控制 (ABAC) 定义与注意事项指南》,2014 年 1 月(含截至 2019 年 8 月的更新)。_
* * *
* * *
免费学习编程。freeCodeCamp 的开源课程已帮助超过 40,000 人获得开发者工作。[立即开始](https://www.freecodecamp.org/learn)