任何处理用户数据的应用程序最终都会遇到同一个问题:并非所有用户都应该看到相同的信息。

初级护士不应该能够访问医院里所有的患者记录,承包商也不应该能够阅读内部的财务报告,而那些在凌晨2点从未知设备登录的员工,显然也不应该被允许编辑生产配置文件。

简单的基于角色的权限控制系统能够很好地处理一些显而易见的情况。但随着应用程序规模的扩大以及访问规则变得越来越复杂,这类系统就会开始出现问题。最终人们会创建出越来越多具体的角色,比如finance_viewerfinanceviewer_us_onlyfinance Viewer_us_only_readonly,直到这些角色本身变得难以管理为止。

基于属性的访问控制机制正是为了解决这个问题而设计的。它将思考重点从“这个用户具有什么角色”转变为“我们对于这个用户、这个资源以及当前这种情况了解多少”,并根据所有这些因素来做出访问权限的决定。

在本文中,你将了解到基于属性的访问控制机制的工作原理、它是如何从早期的访问控制模型演变而来的、政策是如何构成的、如何在代码中实现它,以及何时应该使用它。

目录

先决条件

要想充分理解本文的内容,你需要具备以下条件:

  • 对Web认证机制有基本的了解(包括登录、会话管理、令牌等概念)

  • 熟悉应用程序中用户与资源之间的关系

  • 具备阅读JavaScript代码或伪代码的经验

不需要事先掌握访问控制相关的理论知识。

访问控制机制的发展历程

<要理解为什么会有ABAC这种技术出现,就需要先了解它的前身,以及为什么之前的每一代技术都未能达到预期的目标。>

任意访问控制与强制访问控制

早期的访问控制模型源于20世纪60年代和70年代美国国防部的应用需求。根据NIST特别出版物800-162的定义,这两种控制方式分别为任意访问控制(DAC)和强制访问控制(MAC)。

在任意访问控制中,资源的所有者决定谁可以访问该资源。举个例子,你可以自行决定电脑上的某个文件哪些人可以阅读或编辑。而在强制访问控制中,访问权限由中央机构通过“机密”或“绝密”等标签来规定,系统会严格执行这些标签的规定,而不会依赖个别资源所有者来设置权限。

这两种控制方式在最初的设计目的上都是有效的,但它们并不适合现代复杂网络系统的需求。

基于角色的访问控制”>基于角色的访问控制

基于角色的访问控制是一项重大的技术进步。它不是直接为用户分配权限,而是先为用户分配角色,然后再根据这些角色来决定用户的权限范围。例如,在医院中,“护士”“医生”“管理员”和“账单员”等角色各自拥有不同的权限。

这种机制使得系统管理变得更加高效。新员工入职时只需为其分配合适的角色,员工离职后只需取消其相应的角色即可;如果需要调整护士的权限,也只需要更新与护士角色相关的设置即可。

基于角色的访问控制已被广泛采用,至今仍是许多应用场景中的理想选择。然而它也存在一个结构上的缺陷:当权限需求变得更加细致时,就需要创建更多具体的角色。例如,对于那些只能在特定楼层、特定时间段或针对某些类型的记录来查看患者信息的护士来说,就需要为他们设计非常具体的角色,或者组合多个角色才能满足其工作需求。

这种角色的过度细分现象被称为“角色爆炸”,它会使得角色数量急剧增加,从而导致系统管理变得和基于权限的控制方式一样复杂。

什么是基于属性的访问控制?

ABAC是一种逻辑访问控制模型,每一次访问权限的判断都是通过将一系列规则与当前属性的值进行对比来进行的。在角色分配过程中,没有任何信息会被预先计算或缓存起来。每当用户尝试执行某项操作时,系统都会重新评估:根据我们目前所了解的关于这个用户、这个资源以及当前的环境状况,是否应该允许该操作发生?

正因为如此,ABAC具有极高的精确性和动态性。权限不会随着时间的推移而累积,当用户的角色发生变化时,也无需手动调整权限设置——系统会每次都重新评估当前的属性状态。

NIST关于ABAC的指南中正式指出,这种模型既能够实现自主访问控制机制,也能够实现强制访问控制机制,因此它的灵活性要比那些仅支持其中一种控制方式的模型更强。

像Axiomatics这样的公司、众多政府机构,以及那些需要管理跨部门数据共享的大型企业,都纷纷依赖ABAC来确保安全策略能够在复杂的环境中得到有效实施。

ABAC的四大构成要素

所有的ABAC系统都是由四种类型的信息构成的。明确了解这些信息,是理解ABAC工作原理的关键。

1. 主体属性

主体指的是那些请求访问资源的人或事物。通常情况下,主体是指用户,但也可以是服务、应用程序或自动化系统——NIST将这些对象称为“非个人实体”(Non-Person Entity,简称NPE)。

主体属性用于描述主体的身份信息:

user.jobTitle         = "执业护士"
user.department       = "心脏病科"
user.clearanceLevel   = "机密级"
user.employmentStatus = "在职"
user.location         = "3楼"
user.shiftActive      = true

这些属性通常来源于身份认证系统、人力资源管理系统或用户目录。它们是关于用户的实际信息,可以用来制定访问控制策略。

2. 对象属性(资源属性)

对象指的是主体试图访问的资源。这个资源可以是一个文件、一条数据库记录、一个API接口、一项服务,或是任何其他需要被保护的资源。

对象属性用于描述该资源的具体性质:

record.type           = "PatientMedical"
record.floor          = "3楼"
record.sensitivity    = "高"
record.owner          = "威廉姆斯医生"
record.department     = "心脏病科"

对象属性通常在资源创建时被设置,并会在其整个生命周期中保持不变。这些属性决定了哪些人可以访问该资源。

3. 动作属性

动作指的是主体试图对对象执行的操作。常见的动作包括读取、写入、编辑、删除、复制、执行和共享等。

action.type           = "read"
action.bulk           = false

政策可以独立于其他属性来限制允许执行的动作。例如,即使用户的其他属性都符合要求,他们也可能只被允许读取文档而无法删除它。

4. 环境条件

环境条件是一些与主体或对象无关的情境因素,但它们会影响访问决策的结果。NIST将这类因素描述为“独立的动态因素,在做出访问决策时可以将其作为属性加以考虑”。

例如:

environment.time           = "14:30"
environment.dayOfWeek      = "星期三"
environment.userLocation   = "公司办公室"
environment.ipAddress      = "192.168.1.10"
environment.deviceStatus   = "合规"
environment.threatLevel    = "低"

正是环境条件使得ABAC系统真正具备了动态性。同一个用户、同一个资源、同一个动作,在工作时间内使用受信任的设备时可能被允许执行,但到了午夜时分,如果从未知的IP地址进行访问,则可能会被拒绝。

ABAC决策的制定过程

当主体尝试对某个对象执行某项操作时,ABAC系统会按照以下步骤进行处理:

步骤1:收集属性信息

系统会收集与主体、对象、动作以及环境相关的当前属性信息。这可能包括查询用户目录、读取资源元数据,以及检查当前的时间和位置等信息。

步骤2:查找适用的策略

系统会确定哪些策略适用于当前的请求。例如,一个读取患者记录的请求可能会涉及到多项策略:一项关于临床工作人员访问权限的策略,一项关于非工作时间访问权限的策略,以及一项关于记录敏感度级别的策略。

步骤3:评估每项策略

每一项适用的策略都会根据收集到的属性信息来决定是否允许该操作被执行。

步骤4:消除冲突

如果同时适用多条策略且这些策略之间存在冲突,系统会使用预先定义的规则来决定如何处理这些冲突。常见的处理方式包括“拒绝优先”(只要有一条策略规定拒绝访问,请求就会被拒绝)或“允许优先”(只要有一条策略规定允许访问,请求就会被允许)。

步骤5:执行决策结果

系统会根据最终做出的决定来决定是否允许访问。

每次有访问请求发生时,这一过程都会被重复执行。系统中不会缓存角色分配信息或预先计算出的权限表。决策结果反映的始终是请求发生时刻所有属性的当前状态。

如何编写ABAC策略

策略是ABAC机制的核心所在。它们被编写成以属性为参考条件的规则形式。一份编写得当的策略读起来就像是商业规则,因为它本质上就是商业规则。

简单的布尔逻辑策略

最基础的策略形式就是检查某些属性是否满足条件:

// 策略:只有在职员工才能访问内部资源
function canAccessInternalResource(user) {
  return user.employmentStatus === "Active";
}

这种策略的作用是:在允许访问之前,会检查用户的就业状态这一属性。任何处于非在职状态、被暂停工作或已被解雇的用户,无论他们的角色是什么或过去是否有访问权限,都会被拒绝访问。

多属性策略

在实际应用中,策略通常会结合多个属性来进行判断:

// 策略:只有当护士在指定的楼层工作且当前处于值班时间时,才能查看患者的病历记录
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评估引擎,将上述各个要素整合在一起。

步骤1:定义属性结构

首先,为你的各种属性明确定义数据结构:


// 请求访问权限的用户
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
};

步骤2:编写策略函数

将各种权限控制规则编写成纯函数,这些函数接受相应的属性作为参数,并返回布尔值:


// 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; // 低敏感度的记录不需要满足设备要求
}

其功能如下: 每项策略都是一种简洁、专注的功能。这样一来,这些策略既便于单独进行测试,也易于理解,并且可以在不同的访问控制场景中重复使用。例如,“该用户是否属于临床工作人员”这一策略就可以应用于多种类型的资源。

步骤 3:构建评估引擎

将各项策略整合到一个决策引擎中:

// 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(`策略评估错误:${error.message}`);
      return false; // 出现错误时直接拒绝访问
    }
  });

  // 如果有任何策略返回“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(`访问权限:${canRead ? "被授予" : "被拒绝"}`);
// → 访问权限被授予(所有条件都满足)

其功能如下: 该引擎会依次执行每一项策略,并传入相应的参数。如果所有策略的判断结果都是“true”,则访问权限会被授予;如果有任何一项策略返回“false”,访问权限就会被拒绝。这种机制被称为“基于规则优先级的访问控制”。try-catch语句确保了:一旦有策略出现错误,访问请求就会立即被拒绝,从而遵循了“出现错误时立即采取安全措施”的原则。

步骤 4:添加属性收集功能

在实际应用中,各种属性往往来自不同的数据源:

// attributes/collector.js

async function collectAttributes(userId, resourceId) {
  // 为提高效率,同时从多个来源获取属性
  const [user, resource, environment] = await Promise.all([
    fetchUserAttributes(userId),      // 从身份认证系统或人力资源管理系统获取用户信息
    fetchResourceAttributes(resourceId), // 从资源元数据存储库中获取资源信息
    collectEnvironmentConditions()    // 获取当前环境信息,如时间、IP地址、设备状态等
  });

  return { user, resource, environment };
}

async function fetchUserAttributes(userId) {
  // 该函数会查询用户目录、LDAP系统或身份认证系统以获取用户信息
  const user = await userDirectory.findById(userId);
  const shift = await shiftService.activeShift(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系统能够自动扩展用户的访问权限。例如,急诊医生在提供即时救治时,会自动获得更广泛的访问权限来查阅患者记录,但这些权限是有限时间内的,并且会受到严格监控。

在基于角色的访问控制系统中,所有这些规则都需要设立数十个角色,而这些角色仍然难以动态地处理紧急访问场景。

企业数据访问

大型企业的员工分布在不同的部门、担任不同的职务,位于不同的地点,且具有不同的权限等级,因此他们需要对相同的基础数据查看不同的内容。例如,一份文件可能在工作时间仅对美国地区的财务经理可见,而全球的高管可以随时访问它,但承包商则完全无法访问该文件。

ABAC通过策略来体现所有这些规则。当员工更换部门、休假或调整职务时,他们的属性会在身份系统中得到更新,相应的访问权限也会自动发生变化,无需人工修改访问控制列表。

政府与机密信息

美国联邦政府采用ABAC机制的情况在NIST SP 800-162中有所规定,该标准是为满足联邦身份、凭证及访问管理的需求而制定的。联邦机构处理的信息往往跨越不同的组织部门,具有不同的保密等级和访问要求。

ABAC允许一个机构的分析师访问另一个机构的信息,而无需第二个机构预先为该分析师创建账户。在请求访问时,会根据分析师的权限属性、所属机构以及所负责的项目,来评估其是否具备访问相应资源的资格,并依据相应的规则来确定其访问权限。

多租户SaaS应用

为多个组织提供服务的SaaS应用需要在确保各租户之间数据严格隔离的同时,支持每个租户内部复杂的权限结构。

ABAC能够自然地解决这一问题。例如,当检查record.tenantId这一资源属性与user.tenantId这一用户属性是否匹配时,系统会通过策略来阻止跨租户的访问行为。在同一个租户内部,ABAC可以根据该租户的具体政策要求,支持任意复杂的权限设置。

企业级ABAC实施注意事项

在企业规模上部署ABAC会带来一些在小规模应用中不存在的挑战。

策略管理

策略的制定、审核、测试和部署都是必不可少的环节。根据NIST SP 800-162的标准,这需要专门用于创建和管理策略的工具——策略管理点。如果没有合适的工具支持,策略的管理和维护将会变得非常困难。

在实际操作中,应该将策略视为代码来对待:进行版本控制、代码审查和自动化测试。

属性的质量与时效性

ABAC的有效性取决于其所依赖的属性数据。如果用户属性信息已经过时——比如某个员工更换了部门,但其目录信息尚未更新——那么基于这些过时信息的访问决策就会出错。

NIST警告称,“那些不经常被更新的属性,最终会比那些能够实时更新的属性更不安全。”从权威来源构建可靠的属性处理流程,往往是实施ABAC系统时最困难的部分。

性能

针对每个请求都进行策略评估会带来性能上的开销。每次评估可能都需要从多个来源获取相关属性数据。为了解决这个问题,许多实现方案都会使用属性缓存机制,但缓存也会导致上述提到的“数据过时”问题。

解决这个问题的方法是根据各种属性数据的变更频率来设置合适的缓存有效期。例如,用户的部门信息很少会发生变化,因此可以将其缓存数小时;而用户当前的工作班次状态可能每8小时就会更新一次,因此需要使用更短的缓存时长;至于实时位置信息,则根本不适合进行缓存。

审计日志记录

由于ABAC系统是动态做出访问控制决策的,因此在进行审计时,不仅需要记录下被用于这些决策的属性数据,还需要了解具体的决策过程。如果日志中只简单地写着“访问被拒绝”,那么这种记录是毫无用的——只有同时记录下为什么访问会被拒绝,以及哪些属性没有满足相应的政策要求,这些日志才能起到实际作用。

NIST指出,如果不跟踪在决策时刻各属性的具体值,就无法满足问责制的要求。

局限性与挑战

ABAC系统确实功能强大,但它并不是解决所有访问控制问题的最佳方案。在决定是否实施该系统之前,认清它的局限性是非常重要的。

复杂性:根据NIST SP 800-162的标准,“ABAC系统比那些更简单的访问控制系统更为复杂,因此其实施和维护成本也更高。”正是这种灵活性使得ABAC系统功能强大,但同时也使得人们更难理解它的运作原理。当用户询问“为什么我无法访问这个资源?”时,就需要仔细分析所有被评估的属性数据,以及哪些条件没有得到满足。

策略冲突:在那些包含众多访问控制策略的复杂系统中,不同策略之间可能会出现冲突。某些单独来看是合理的策略,当结合在一起使用时,可能会产生意想不到的结果。要解决这些冲突,就需要制定明确的优先级规则,并仔细设计各种策略。

属性管理开销:要想在庞大的用户群体中维护准确的属性数据,就必须投资于身份识别相关的基础设施。来自不同系统的属性数据需要被标准化、验证,并保持同步。正如NIST所指出的,组织所需要的不仅仅是一个政策执行引擎,而是一套完整的属性管理基础设施。

测试难度大:由于访问权限的判定往往取决于数十个属性的组合,因此要全面测试各种边缘情况需要耗费大量的精力。对于那些在常见情况下能够正常运行的策略,当遇到不常见的属性组合时,它们的行为可能会发生意外变化。

并不总是值得投资:对于那些访问控制需求相对简单的应用来说,ABAC系统会带来不必要的复杂性。如果你的需求可以通过一组具有固定权限的角色来清晰地表达出来,那么RBAC系统才是更合适的选择。

结论

基于属性的访问控制代表了应用程序在权限管理方式上的真正进步。与维护日益庞大的角色与权限列表不同,ABAC会在每次请求发生时,根据用户、资源及环境的具体情况来评估相应的权限需求。

它有效解决了复杂RBAC实现中普遍存在的“角色膨胀”问题,使访问规则能够真实反映业务政策而非技术上的近似表达。对于动态场景、紧急情况、基于时间的限制条件,以及跨组织访问需求,ABAC都能提供有效的解决方案——而这些都是静态角色模型难以处理的。

然而,并非所有情况下ABAC都更优。它的实现难度更高,调试也更复杂,同时还需要投资建设属性管理基础设施,而这类设施在简单模型中并非必需。许多应用程序使用RBAC就已经能够满足需求,也有些应用会结合使用RBAC和ABAC。

正确的问题应该是“我的访问控制需求是否足够复杂,以至于投资ABAC是值得的?”如果你的访问规则频繁变化,或者这些规则需要根据资源或环境条件来调整,又或者需要在不同组织间进行应用,那么ABAC确实值得认真考虑。

首先,你需要找出当前访问控制模型在哪些地方存在缺陷。如果你为了覆盖所有极端情况而创建了大量角色,如果在路由处理程序中编写了用于检查特定属性值的条件逻辑,或者用户获得了本不应拥有的权限,这些都属于表明需要采用更强大的模型的信号。

当传统的角色模型无法满足需求时,ABAC就是理想的解决方案。

术语表

ABAC(基于属性的访问控制):这是一种通过评估主体、对象、操作及环境条件的属性来决定授权是否允许的访问控制方法。根据NIST的定义,这种机制“是根据主体被赋予的属性来决定是否允许其对对象执行相应操作”。

主体:请求访问资源的实体。通常是人类用户,但也可能是服务、自动化流程或设备。也被称为“请求者”。

对象:需要被保护的资源,例如文件、数据库记录、API接口、服务,或是任何由ABAC系统管理访问权限的系统资源。

属性:主体、对象、操作或环境条件的特征,通常以键值对的形式表示。例如:user.department = "Finance"record.sensitivity = "High"

主体属性:描述发起请求的用户或服务的属性,如职位、部门、安全等级或当前位置等。

对象属性:描述被访问资源的属性,如其类型、所有者、敏感度等级或所属部门等。

环境条件:那些与主体和对象都无关的、会影响访问决策的环境因素。例如一天中的时间、一周中的某天、IP地址、设备的合规状态或当前的威胁等级等。
策略:一种规则或规则集,用于评估属性值以确定是否应允许特定的访问请求。ABAC策略通常以逻辑条件的形式来表述。
策略决策点:ABAC系统中的那个组件,它负责评估策略和属性值,从而做出访问决策。
策略执行点:拦截访问请求并执行策略决策点所做出的决定的组件。
策略信息点:为策略决策点获取所需属性值的组件。
策略管理点:提供创建、测试和管理策略功能的接口的组件。
基于角色的访问控制:一种访问控制模型,它将权限分配给角色,再由角色将这些权限分配给用户。这种模型比ABAC简单,但在处理复杂且动态的访问需求时表现较差。
角色数量激增:当访问需求变得越来越详细时,基于角色的访问控制系统中的角色数量也会急剧增加,最终导致这些角色变得和单独的权限一样难以管理。
自主访问控制:一种访问控制模型,在这种模型中,资源所有者可以决定谁能够访问他们的资源。这种机制在文件系统中较为常见。
强制访问控制:一种访问控制模型,其中访问权限由中央机构通过分类标签来规定,而与资源所有者的意愿无关。
访问控制列表:与资源相关联的列表,用于指定哪些用户或组具有哪些权限。这种机制在基于身份的访问控制系统中被广泛使用。
非人类主体:指那些不是人类用户的实体,例如自动化服务、应用程序或网络设备,它们也可以请求访问资源。
属性缓存:将之前获取过的属性值存储起来以提高性能,但这样做可能会导致在做出访问决策时使用到过时的数据。
拒绝优先规则:一种策略组合规则,如果有任何适用的策略返回“拒绝”结果,那么最终的决策就是“拒绝”,而不会考虑其他可能返回“允许”结果的策略。
故障关闭机制:一种安全设计原则,当出现意外错误或信息缺失时,系统会直接拒绝访问请求,而不是允许访问,从而降低未经授权访问的风险。
来源:这些定义改编自NIST于2014年1月发布的《基于属性的访问控制定义与考虑因素指南》(特别出版物800-162),并经过了截至2019年8月的更新。

Comments are closed.