Bootstrap

SpringSecurity中文文档(Domain Object Security (ACLs))

Domain Object Security (ACLs)

本节描述 SpringSecurity 如何使用访问控制列表(ACL)提供域对象安全性。

复杂的应用程序通常需要定义超出 Web 请求或方法调用级别的访问权限。相反,安全决策需要包括 who (Authentication)、 where (MethodInvation)和 what (Some DomainObject)。换句话说,授权决策还需要考虑方法调用的实际域对象实例主题。

假设您正在为宠物诊所设计一个应用程序。基于 Spring 的应用程序的用户主要有两类: 宠物诊所的工作人员和宠物诊所的客户。员工应该可以访问所有的数据,而客户应该只能看到他们自己的客户记录。为了让它更有趣一点,您的客户可以让其他用户查看他们的客户记录,例如他们的“幼儿园”导师或当地“小马俱乐部”的主席。当您使用 Spring Security 作为基础时,有几种可能的方法:

  • 编写业务方法以加强安全性。您可以查询 Customer 域对象实例中的集合,以确定哪些用户具有访问权限。通过使用 SecurityContextHolder.getContext().GetAuthentication() ,可以访问 Authentication 对象。
  • 编写一个 AccessDecisionVoter 来强制存储在 Authentication 对象中的 GrantedAuthority []实例的安全性。这意味着 AuthenticationManager 需要用自定义 GrantedAuthority []对象填充 Authentication,以表示主体可以访问的每个 Customer 域对象实例。
  • 编写一个 AccessDecisionVoter 来加强安全性,并直接打开目标 Customer 域对象。这意味着您的投票者需要访问一个 DAO,该 DAO 允许它检索 Customer 对象。然后,它可以访问 Customer 对象的已批准用户集合,并做出适当的决策。

这些方法中的每一种都是完全合法的。但是,第一种方法将授权检查与业务代码结合起来。这样做的主要问题包括增加了单元测试的难度,以及在其他地方重用 Customer 授权逻辑更加困难。从 Authentication 对象获取 GrantedAuthority []实例也可以,但不能扩展到大量 Customer 对象。如果一个用户可以访问5,000个 Customer 对象(在这种情况下不太可能,但是想象一下如果它是一个大型 Pony Club 的流行兽医!)消耗的内存量和构造 Authentication 对象所需的时间将是不可取的。最后一种方法,从外部代码直接打开 Customer,可能是这三种方法中最好的一种。它实现了关注点分离,并且不会误用内存或 CPU 周期,但它仍然效率低下,因为 AccessDecisionVoter 和最终的业务方法本身都会调用负责检索 Customer 对象的 DAO。每个方法调用两个访问显然是不可取的。此外,对于列出的每种方法,您都需要从头开始编写自己的访问控制列表(ACL)持久性和业务逻辑。

幸运的是,还有另一种选择,我们稍后将讨论。

Key Concepts

Spring Security 的 ACL 服务在 Spring-Security-ACL-xxx.jar 中提供。您需要将此 JAR 添加到类路径中,以使用 Spring Security 的域对象实例安全性功能。

SpringSecurity 的域对象实例安全性能集中在访问控制列表(ACL)的概念上。系统中的每个域对象实例都有自己的 ACL,ACL 记录谁可以使用该域对象、谁不能使用该域对象的详细信息。考虑到这一点,Spring Security 为您的应用程序提供了三种主要的 ACL 相关功能:

  • 有效检索所有域对象的 ACL 条目(并修改这些 ACL)的方法
  • 一种确保在调用方法之前允许给定主体使用对象的方法
  • 一种确保在调用方法后允许给定主体使用对象(或它们返回的内容)的方法

正如第一个要点所指出的,Spring Security ACL 模块的主要功能之一是提供检索 ACL 的高性能方法。这种 ACL 存储库功能非常重要,因为您系统中的每个域对象实例可能有多个访问控制条目,并且每个 ACL 可能以树形结构从其他 ACL 继承(Spring Security 支持这种功能,它非常常用)。Spring Security 的 ACL 功能经过精心设计,可以提供高性能的 ACL 检索,以及可插入缓存、死锁——最小化数据库更新、独立于 ORM 框架(我们直接使用 JDBC)、适当的封装和透明的数据库更新。

考虑到数据库是 ACL 模块操作的核心,我们需要研究实现中默认使用的四个主要表。在一个典型的 Spring Security ACL 部署中,表按大小顺序显示,最后列出行数最多的表:

  • ACL_SID 允许我们唯一地标识系统中的任何主体或权限(“ SID”代表“安全标识”)。唯一的列是 ID、 SID 的文本表示形式和一个标志,用于指示文本表示形式是引用主体名称还是 GrantedAuthority。因此,每个唯一的主体或 GrantedAuthority 都有一个单独的行。当在接收权限的上下文中使用时,SID 通常称为“接收方”。
  • ACL_CLASS 允许我们唯一地标识系统中的任何域对象类。唯一的列是 ID 和 Java 类名。因此,我们希望为其存储 ACL 权限的每个唯一类都有一行。
  • ACL_OBJECT_IDENTITY 存储系统中每个唯一域对象实例的信息。列包括 ID,ACL _ CLASS 表的一个外键,一个唯一标识符,这样我们就知道了我们提供信息的 ACL_CLASS 实例,父元素,表示域对象实例所有者的 ACL_SID 表的一个外键,以及我们是否允许 ACL 条目从任何父 ACL 继承。对于存储 ACL 权限的每个域对象实例,我们都有一个单独的行。
  • 最后,ACL_ENTRY 存储分配给每个收件人的单独权限。列包括 ACL_OBJECT_IDENTITY 的外键、收件人(即 ACL_SID 的外键) ,不管我们是否要审计,以及表示授予或拒绝的实际权限的整数位掩码。对于每个接收到使用域对象权限的收件人,我们都有一个单独的行。

正如上一段提到的,ACL 系统使用整数位屏蔽。但是,要使用 ACL 系统,您不必知道位移的细节。只要说我们有32位可以开关就足够了。每个位表示一个权限。默认情况下,权限是读(位0)、写(位1)、创建(位2)、删除(位3)和管理(位4)。如果希望使用其他权限,可以实现自己的 Permission 实例,而 ACL 框架的其余部分在不知道扩展的情况下运行。

您应该理解,您系统中域对象的数量与我们选择使用整数位掩码这一事实完全没有关系。虽然您有32位可用于权限,但可能有数十亿个域对象实例(这意味着 ACL _ OBJECT _ IDENTITY 中有数十亿行,可能还有 ACL _ ENTRY)。我们之所以提出这个观点,是因为我们发现,人们有时会错误地认为,他们需要为每个潜在的域对象添加一点,但事实并非如此。

现在,我们已经提供了 ACL 系统的基本概述,以及它在表结构级别的外观,我们需要探索关键的接口:

  • Acl: 每个域对象都有且只有一个 Acl 对象,该对象在内部保存 AccessControlEntry 对象,并且知道 Acl 的所有者。Acl 不直接引用域对象,而是引用 ObjectIdentity。ACL 存储在 ACL _ OBJECT _ IDENTITY 表中。
  • AccessControlEntry: Acl 包含多个 AccessControlEntry 对象,这些对象在框架中通常缩写为 ACE。每个 ACE 都引用 Permission、 Sid 和 Acl 的特定元组。ACE 还可以授予或不授予权限,并包含审计设置。ACE 存储在 ACL _ ENTRY 表中。
  • ObjectIdentity: 每个域对象在 ACL 模块内部由 ObjectIdentity 表示。
  • AclService: 检索适用于给定 ObjectIdentity 的 Acl。在所包含的实现(JdbcAclService)中,检索操作委托给 LookupStrategy。LookupStrategy 为检索 ACL 信息提供了高度优化的策略,使用批量检索(BasicLookupStrategy) ,并支持使用物化视图、分层查询和类似的以性能为中心的非 ANSI SQL 功能的自定义实现。
  • MutableAclService: 允许为持久性显示修改后的 Acl。此接口的使用是可选的。

请注意,我们的 AclService 和相关数据库类都使用 ANSI SQL。因此,这应该适用于所有主要的数据库。在写这篇文章的时候,系统已经成功地用 Hypersonic SQL,postgreSQL,Microsoft SQL Server 和 Oracle 进行了测试。

SpringSecurity 附带了两个示例,演示了 ACL 模块。第一个示例是联系人示例,另一个示例是文档管理系统(DMS)示例。我们建议看看这些例子。

Getting Started

要开始使用 Spring Security 的 ACL 功能,您需要将 ACL 信息存储在某个地方。这就需要在 Spring 中实例化 DataSource。然后将 DataSource 注入到 JdbcMutableAclService 和 BasicLookupStrategy 实例中。前者提供了 mutator 功能,后者提供了高性能的 ACL 检索功能。有关示例配置,请参阅 SpringSecurity 附带的一个示例。还需要使用前一节中列出的四个 ACL 特定表填充数据库(有关适当的 SQL 语句,请参阅 ACL 示例)。

创建了所需的模式并实例化了 JdbcMutableAclService 之后,需要确保域模型支持与 Spring Security ACL 包的互操作性。希望 ObjectIdentityImpl 证明是足够的,因为它提供了大量使用它的方法。大多数人都有包含公共 SerializablegetId ()方法的域对象。如果返回类型为 long 或与 long (如 int)兼容,则可能会发现无需进一步考虑 ObjectIdentity 问题。ACL 模块的许多部分依赖于长标识符。如果不使用 long (或 int、 byte 等) ,则可能需要重新实现许多类。我们不打算在 Spring Security 的 ACL 模块中支持非长标识符,因为 long 已经与所有数据库序列兼容,是最常见的标识符数据类型,并且具有足够的长度来适应所有常见的使用场景。

下面的代码片段显示了如何创建 Acl 或修改现有的 Acl:

// Prepare the information we'd like in our access control entry (ACE)
ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
Sid sid = new PrincipalSid("Samantha");
Permission p = BasePermission.ADMINISTRATION;

// Create or update the relevant ACL
MutableAcl acl = null;
try {
acl = (MutableAcl) aclService.readAclById(oi);
} catch (NotFoundException nfe) {
acl = aclService.createAcl(oi);
}

// Now grant some permissions via an access control entry (ACE)
acl.insertAce(acl.getEntries().length, p, sid, true);
aclService.updateAcl(acl);

在上面的示例中,我们检索与标识符号为44的 Foo 域对象关联的 ACL。然后,我们添加一个 ACE,以便名为“ Samantha”的主体可以“管理”对象。代码片段相对来说是自解释的,除了 insert tAce 方法之外。Insert tAce 方法的第一个参数确定插入新条目的 Acl 中的位置。在上面的示例中,我们将新 ACE 放在现有 ACE 的末尾。最后一个参数是一个布尔值,指示 ACE 是授予还是拒绝。大多数情况下,它授予(真实)。但是,如果拒绝(假) ,则实际上阻止了权限。

Spring Security 没有提供任何特殊的集成来自动创建、更新或删除 ACL,将其作为 DAO 或存储库操作的一部分。相反,您需要为各个域对象编写类似于前面示例中所示的代码。您应该考虑在服务层上使用 AOP 来自动地将 ACL 信息与服务层操作集成在一起。我们发现这种方法是有效的。

一旦您使用这里描述的技术在数据库中存储了一些 ACL 信息,下一步就是实际使用 ACL 信息作为授权决策逻辑的一部分。你有很多选择。您可以编写自己的 AccessDecisionVoter 或 AfterInvocationProvider (分别)在方法调用之前或之后触发。这样的类将使用 AclService 检索相关的 ACL,然后调用 ACL.isGranted (Permission [] mission,Sid [] sids,boolean Administration ativeMode)来决定是否授予或拒绝授权。或者,您可以使用我们的 AclEntryVoter、 AclEntryAfterInvocationProvider 或 AclEntryAfterInvocationCollectionFilteringProvider 类。所有这些类都提供了一种基于声明的方法来在运行时计算 ACL 信息,从而使您无需编写任何代码。

请参阅示例应用程序以了解如何使用这些类。

;