Contents

访问控制模型

1. 简介

在本文中,我们将探讨不同的访问控制模型如何在实践中实现它们。

2. 什么是访问控制模型?

应用程序,尤其是基于 Web 的应用程序的一个常见要求是,只有在满足一组给定条件(也称为策略)时才能执行某些操作。好的,这是一个很笼统的要求,所以我们举几个例子:

  • 互联网论坛:只有成员才能发布新消息或回复现有消息
  • 电商网站:普通用户只能看到自己的订单
  • 银行后台:客户经理可以管理他/她自己客户的投资组合。除了这些投资组合之外,他/她还可以在他/她暂时不可用(例如,假期)并且前者充当其同行时管理另一个客户经理的客户的投资组合。
  • 数字钱包:在用户所在时区的 20:00 至 08:00 期间,付款限额为 500 美元

我们将为给定应用程序采用的访问控制模型将负责评估传入的请求并做出决定:请求是否可以继续。在后一种情况下,结果通常是返回给用户的错误消息。

/uploads/java_access_control_models/1.png

显然,在授权给定请求时,这些示例中的每一个都需要不同的方法。

3. 访问控制模型类型

从前面的示例中,我们可以看到,要做出允许/拒绝决定,我们需要考虑与请求相关的不同方面:

  • 与请求关联的身份。请注意,即使匿名访问在这里也有一种身份形式
  • 请求所针对的对象/资源
  • 对这些对象/资源执行的操作
  • 有关请求的上下文信息。一天中的时间、时区和使用的身份验证方法是此类上下文信息的示例

我们可以将访问控制模型分为三种类型:

  • 基于角色的访问控制 (RBAC)
  • 访问控制列表 (ACL)
  • 基于属性的访问控制 (ABAC)

无论其类型如何,我们通常可以识别模型中的以下实体:

  • PEPPolicy Enforcement Point :根据PDP返回的结果拦截请求并让它继续执行或不执行
  • PDPPolicy Decision Point:使用策略评估请求以产生访问决策
  • PIPPolicy Information Point:存储和/或调解对 PDP 用于做出访问决策的信息的访问。
  • PAPPolicy Administration Point:管理与访问决策相关的策略和其他操作方面。

下图显示了这些实体如何在逻辑上相互关联:

/uploads/java_access_control_models/3.png

重要的是要注意,虽然被描述为自治实体,但在实践中,我们会发现一些甚至所有模型元素都嵌入在应用程序本身中。

此外,该模型没有解决如何建立用户身份的问题。然而,在决定是否允许请求继续进行时,可以考虑这一方面。

现在,让我们看看如何将这种通用架构应用于上述每个模型。

4. 基于角色的访问控制

在该模型中,PDP 决策过程包括两个步骤:

  • 首先,它恢复与传入请求的身份相关的角色。
  • 接下来,它尝试将这些角色与请求策略相匹配

该模型的具体实现在 Java EE 规范中,以@HttpConstraint*注解及其 XML 等价物*的形式出现。这是应用到 servlet 时注解的典型用法:

@WebServlet(name="rbac", urlPatterns = {"/protected"})
@DeclareRoles("USER")
@ServletSecurity(
@HttpConstraint(rolesAllowed = "USER")
)
public class RBACController  extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("Hello, USER");
    }
}

对于Tomcat服务器,我们可以识别出之前描述的访问控制模型实体如下:

  • PEP:安全Valve,检查目标 servlet 中是否存在此注解并调用关联的Realm以恢复与当前请求关联的身份
  • PDP:决定对给定请求应用哪些限制的领域实现
  • PIP:存储安全相关信息的特定Realm实现使用的任何后端。对于 RBAC 模型,关键信息是用户的角色集,通常从 LDAP 存储库中检索。
  • Policy Store:在这种情况下,注解就是存储本身
  • PAP:Tomcat 不支持动态策略更改,因此没有真正的需要。但是,如果有一些想象,我们可以使用任何用于添加注解和/或编辑应用程序的 WEB-INF/web.xml 文件的工具来识别它。

其他安全框架(例如,Spring Security)以类似的方式工作。这里的关键点是,即使一个特定的框架不完全符合我们的通用模型,它的实体仍然存在,即使有些伪装。

4.1. 角色定义

究竟什么是角色?实际上,角色只是用户可以在特定应用程序中执行的一组命名的相关操作。根据应用程序的要求,可以根据需要对它们进行粗略或精细定义。

无论它们的粒度级别如何,定义它们都是一个好习惯,因此每个都映射到一组不相交的功能。这样,我们可以通过添加/删除角色轻松管理用户配置文件,而无需担心副作用。

至于用户和角色之间的关联,我们可以采用直接或间接的方式。在前者中,我们将角色直接分配给用户。在后者中,有一个中间实体,通常是一个用户组,我们为其分配角色:

/uploads/java_access_control_models/5.png

**在这种关联中使用组作为中间实体的好处是我们可以轻松地一次将角色重新分配给许多用户。**这一方面在大型组织的背景下非常相关,在这些组织中,人们不断地从一个区域移动到另一个区域。

同样,间接模型还允许我们轻松更改现有角色定义,通常是在重构应用程序之后。

5. 访问控制列表

**基于 ACL 的安全控制允许我们定义对单个域对象的访问限制。**这与 RBAC 形成对比,RBAC 的限制通常适用于整个对象类别。在上面的论坛示例中,我们可以使用仅 RBAC 的方法来定义可以阅读和创建新帖子。

但是,如果我们决定创建一个新功能,让用户可以编辑自己的帖子,那么仅 RBAC 是不够的。在这种情况下,决策引擎不仅需要考虑谁,还要考虑哪个帖子是编辑操作的目标。

对于这个简单的示例,我们只需向数据库中添加一个作者列,并使用它来允许或拒绝对编辑操作的访问。但是,如果我们想支持协作编辑怎么办?在这种情况下,我们需要存储可以编辑帖子的所有人的列表——ACL。

处理 ACL 会带来一些实际问题:

  • 我们在哪里存储 ACL?
  • 检索大型对象集合时如何有效地应用 ACL 限制?

Spring Security ACL 库 是 ACL 库的一个很好的例子。它使用专用的数据库模式和缓存来实现 ACL,并与 Spring Security 紧密集成。这是一个简短的例子,改编自我们关于这个库的文章 ,展示了如何在对象级别实现访问控制:

@PreAuthorize("hasPermission(#postMessage, 'WRITE')")
PostMessage save(@Param("noticeMessage")PostMessage postMessage);

ACL 的另一个很好的例子是 Windows 用来保护对象的权限系统。每个*securable-objects (例如,文件、目录、进程等)都有一个附加的security-descriptors *,其中包含单个用户/组和相关权限的列表:

/uploads/java_access_control_models/7.png

Windows ACL 非常强大(或复杂,取决于我们询问的对象),允许管理员将权限分配给单个用户和/或组。此外,各个条目为每个可能的操作定义允许/拒绝权限。

6. 基于属性的访问控制

基于属性的控制模型允许访问决策不仅基于身份、操作和目标对象,还基于与请求相关的上下文信息XACML 标准可能是该模型最著名的示例,它使用 XML 文档来描述访问策略。这就是我们可以使用这个标准来描述数字钱包提款规则的方式:

<Policy xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17" 
  PolicyId="urn:blogdemo:atm:WithdrawalPolicy"
  Version="1.0" 
  RuleCombiningAlgId="urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:deny-overrides">
    <Target/>
    <Rule RuleId="urn:oasis:names:tc:blogdemo:WithDrawalPolicy:Rule1" Effect="Deny">
        <Target>
            <AnyOf>
                <AllOf>
... match rule for the withdrawal action omitted
                </AllOf>
            </AnyOf>
        </Target>
        <Condition>
... time-of-day and amount conditions definitions omitted
        </Condition>
    </Rule>
</Policy>

尽管它很冗长,但不难弄清楚它的一般结构。策略包含一个或多个规则,在评估时会产生Effect: Permit 或 Deny

每个规则都包含目标,这些目标使用请求的属性定义逻辑表达式。或者,规则还可以包含一个或多个定义其适用性的Condition元素。

在运行时,基于 XACML 的访问控制 PEP 创建一个RequestContext实例并将其提交给 PDP 进行评估。然后引擎评估所有适用的规则并返回访问决策。

RequestContext中存在的信息类型是此模型与前面模型的主要区别。举个例子,一个请求上下文的 XML 表示形式是为了在我们的数字钱包应用程序中授权提款而构建的:

<Request 
    xmlns="urn:oasis:names:tc:xacml:3.0:core:schema:wd-17"
    CombinedDecision="true"
    ReturnPolicyIdList="false">
    
    <Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:action">
... action attributes omitted
    </Attributes>
    <Attributes Category="urn:oasis:names:tc:xacml:3.0:attribute-category:environment">
... environment attributes (e.g., current time) omitted
    </Attributes>
    <Attributes Category="urn:blogdemo:atm:withdrawal">
... withdrawal attributes omitted 
    </Attributes>
</Request>

**ABAC 模型的主要优势在于其灵活性。**我们可以简单地通过更改策略来定义并且更重要的是修改复杂的规则。根据实现的不同,我们甚至可以实时进行。

6.1. XACML4J

XACML4J 是用于 Java 的 XACML 3.0 标准的开源实现。它提供了ABAC模型所需的评估引擎和相关实体的实现。它的核心 API 是PolicyDecisionPoint接口,毫不奇怪,它实现了 PDP 逻辑。

一旦我们构建了 PDP,使用它需要两个步骤。首先,我们使用有关我们要评估的请求的信息创建并填充一个RequestContext

... attribute categories creation omitted
RequestContext request = RequestContext.builder()
  .attributes(actionCategory,environmentCategory,atmTxCategory)
  .build();

接下来,我们将此对象传递给PolicyDecisionPoint服务的 *decision()*方法 进行评估:

ResponseContext response = pdp.decide(request);
assertTrue(response.getDecision() == Decision.DENY);

返回的ResponseContext包含一个带有策略评估结果的Decision对象。此外,它还可能向 PEP 返回诊断信息和其他义务和/或建议。义务和建议本身就是一个主题,所以我们不会在这里讨论它们。Axiomatic 的教程展示了我们如何使用此功能在典型的记录系统应用程序中实施监管控制。

6.2. ABAC 没有 XACML

**XACML 的复杂性通常使其对大多数应用程序来说太过分了。**但是,我们仍然可以在应用程序中使用底层模型。毕竟,我们总是可以实现一个针对特定用例量身定制的更简单的版本,也许只是外部化几个参数,对吧?

好吧,任何经验丰富的开发人员都知道这会如何结束……

任何 ABAC 实现的一个棘手方面是如何从请求的有效负载中提取属性。在处理请求之前插入自定义逻辑的标准方法,例如 servlet 过滤器或 JAX-RS 拦截器只能访问原始有效负载数据。

由于现代应用程序倾向于使用 JSON 或类似的表示,PEP 必须在提取任何有效负载属性之前对其进行解码。这意味着对 CPU 和内存使用的潜在影响,尤其是对于大型有效负载。

在这种情况下,更好的方法是使用 AOP 来实现 PEP。在这种情况下,方面处理程序代码可以访问有效负载的解码版本。