此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.3spring-doc.cadn.net.cn

域对象安全性 (ACL)

概述

复杂的应用程序通常会发现需要定义访问权限,而不仅仅是在 Web 请求或方法调用级别。 相反,安全决策需要包括谁 (Authentication),其中 (MethodInvocation) 和什么 (SomeDomainObject). 换句话说,授权决策还需要考虑方法调用的实际域对象实例主题。spring-doc.cadn.net.cn

假设您正在为宠物诊所设计应用程序。 基于 Spring 的应用程序将有两组主要用户:宠物诊所的工作人员以及宠物诊所的客户。 员工将可以访问所有数据,而您的客户将只能看到他们自己的客户记录。 为了更有趣,您的客户可以允许其他用户查看他们的客户记录,例如他们的 “puppy preschool” 导师或他们当地的 “Pony Club” 的主席。 使用 Spring Security 作为基础,您可以使用多种方法:spring-doc.cadn.net.cn

  • 编写您的业务方法来实施安全性。 您可以查阅Customerdomain 对象实例来确定哪些用户具有访问权限。 通过使用SecurityContextHolder.getContext().getAuthentication(),您将能够访问Authentication对象。spring-doc.cadn.net.cn

  • 编写一个AccessDecisionVoter要从GrantedAuthority[]s 存储在Authentication对象。 这意味着您的AuthenticationManager需要填充Authentication带自定义GrantedAuthority[]s 表示每个CustomerDomain 对象实例。spring-doc.cadn.net.cn

  • 编写一个AccessDecisionVoter强制执行安全性并打开目标Customerdomain 对象。 这意味着您的选民需要访问 DAO,以允许它检索Customer对象。 然后,它将访问CustomerObject 的已批准用户集合,并做出适当的决策。spring-doc.cadn.net.cn

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

幸运的是,还有另一种选择,我们将在下面讨论。spring-doc.cadn.net.cn

关键概念

Spring Security 的 ACL 服务在spring-security-acl-xxx.jar. 您需要将此 JAR 添加到 Classpath 中,以使用 Spring Security 的域对象实例安全功能。spring-doc.cadn.net.cn

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

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

鉴于数据库是 ACL 模块作的核心,让我们探索一下实现中默认使用的四个主要表。 下表按典型 Spring Security ACL 部署中的大小顺序显示,行数最多的表列在最后:spring-doc.cadn.net.cn

  • ACL_SID 允许我们唯一标识系统中的任何主体或权威机构(“SID”代表“安全身份”)。 唯一的列是 ID、SID 的文本表示形式和一个标志,用于指示文本表示形式是引用主体名称还是GrantedAuthority. 因此,每个唯一主体或都有一行GrantedAuthority. 在接收权限的上下文中使用时,SID 通常称为“接收者”。spring-doc.cadn.net.cn

  • ACL_CLASS 允许我们唯一标识系统中的任何域对象类。 唯一的列是 ID 和 Java 类名。 因此,我们希望为其存储 ACL 权限的每个唯一类都有一行。spring-doc.cadn.net.cn

  • ACL_OBJECT_IDENTITY 存储系统中每个唯一域对象实例的信息。 列包括 ID、ACL_CLASS表的外键、唯一标识符(以便我们知道要为哪个ACL_CLASS实例提供信息)、父级、ACL_SID表的外键(用于表示域对象实例的所有者)以及我们是否允许 ACL 条目从任何父 ACL 继承。 我们为其存储 ACL 权限的每个域对象实例都有一行。spring-doc.cadn.net.cn

  • 最后,ACL_ENTRY 存储分配给每个收件人的各个权限。 列包括ACL_OBJECT_IDENTITY的外键、接收者(即ACL_SID的外键)、我们是否要审计,以及表示实际授予或拒绝权限的整数位掩码。 对于每个获得使用域对象权限的收件人,我们都有一行。spring-doc.cadn.net.cn

如上一段所述,ACL 系统使用整数位掩码。 别担心,使用 ACL 系统时,您无需了解移位的细节,但只需说我们有 32 位可以打开或关闭就足够了。 这些位中的每一个都代表一个权限,默认情况下,权限为读取(第 0 位)、写入(第 1 位)、创建(第 2 位)、删除(第 3 位)和管理(第 4 位)。 实现自己的 API 很容易Permission实例,ACL 框架的其余部分将在不知道您的扩展的情况下运行。spring-doc.cadn.net.cn

重要的是要了解系统中域对象的数量与我们选择使用 integer bit masking 的事实完全无关。 虽然您有 32 位可用于权限,但您可能有数十亿个域对象实例(这意味着ACL_OBJECT_IDENTITY中有数十亿行,很可能是 ACL_ENTRY)。 我们之所以提出这一点,是因为我们发现有时人们错误地认为他们需要为每个潜在的 domain object 提供一点,但事实并非如此。spring-doc.cadn.net.cn

现在我们已经提供了 ACL 系统功能的基本概述,以及它在表结构中的外观,让我们探索一下关键接口。 主要接口包括:spring-doc.cadn.net.cn

  • Acl:每个域对象都有且只有一个Aclobject,它在内部保存了AccessControlEntry的 S 以及Acl. Acl 不直接引用域对象,而是引用ObjectIdentity. 这Acl存储在 ACL_OBJECT_IDENTITY 表中。spring-doc.cadn.net.cn

  • AccessControlEntry:一Acl持有多个AccessControlEntrys,它们在框架中通常缩写为 ACE。 每个 ACE 都引用一个特定的 TuplesPermission,SidAcl. ACE 也可以是授予或不授予的,并包含审核设置。 ACE 存储在 ACL_ENTRY 表中。spring-doc.cadn.net.cn

  • Permission:权限表示特定的不可变位掩码,并为位掩码和输出信息提供便捷功能。 上面显示的基本权限(位 0 到 4)包含在BasePermission类。spring-doc.cadn.net.cn

  • Sid:ACL 模块需要引用 principals 和GrantedAuthority[]s. 间接级别由Sidinterface,它是 “Security Identity” 的缩写。 常见类包括PrincipalSid(要在Authenticationobject) 和GrantedAuthoritySid. 安全身份信息存储在 ACL_SID 表中。spring-doc.cadn.net.cn

  • ObjectIdentity:每个域对象在 ACL 模块内部由ObjectIdentity. 默认实现称为ObjectIdentityImpl.spring-doc.cadn.net.cn

  • AclService:检索Acl适用于给定的ObjectIdentity. 在包含的实现 (JdbcAclService),检索作将委托给LookupStrategy. 这LookupStrategy提供高度优化的策略来检索 ACL 信息,使用批量检索 (BasicLookupStrategy),并支持利用具体化视图、分层查询和类似的以性能为中心的非 ANSI SQL 功能的自定义实现。spring-doc.cadn.net.cn

  • MutableAclService:允许修改后的Acl呈现以供持久化。 如果您不想,则不必使用此界面。spring-doc.cadn.net.cn

请注意,我们开箱即用的 AclService 和相关数据库类都使用 ANSI SQL。 因此,这应该适用于所有主要数据库。 在撰写本文时,该系统已经使用 Hypersonic SQL、PostgreSQL、Microsoft SQL Server 和 Oracle 成功进行了测试。spring-doc.cadn.net.cn

Spring Security 附带了两个示例,用于演示 ACL 模块。 第一个是联系人示例,另一个是文档管理系统 (DMS) 示例。 我们建议查看这些示例。spring-doc.cadn.net.cn

开始

要开始使用 Spring Security 的 ACL 功能,您需要将 ACL 信息存储在某个位置。 这需要实例化DataSource使用 Spring。 这DataSource然后注入到JdbcMutableAclServiceBasicLookupStrategy实例。 后者提供高性能的 ACL 检索能力,前者提供 mutator 能力。 有关示例配置,请参阅 Spring Security 附带的示例之一。 您还需要使用上一节中列出的四个特定于 ACL 的表填充数据库(有关相应的 SQL 语句,请参阅 ACL 示例)。spring-doc.cadn.net.cn

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

以下代码片段显示了如何创建Acl或修改现有的Acl:spring-doc.cadn.net.cn

// 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);
val oi: ObjectIdentity = ObjectIdentityImpl(Foo::class.java, 44)
val sid: Sid = PrincipalSid("Samantha")
val p: Permission = BasePermission.ADMINISTRATION

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

// Now grant some permissions via an access control entry (ACE)
acl!!.insertAce(acl.entries.size, p, sid, true)
aclService.updateAcl(acl)

在上面的示例中,我们将检索与标识符编号为 44 的 “Foo” 域对象关联的 ACL。 然后,我们将添加一个 ACE,以便名为 “Samantha” 的主体可以 “管理” 该对象。 代码片段相对不言自明,但 insertAce 方法除外。 insertAce 方法的第一个参数是确定新条目将插入到 Acl 中的哪个位置。 在上面的示例中,我们只是将新 ACE 放在现有 ACE 的末尾。 最后一个参数是一个布尔值,指示 ACE 是授予还是拒绝。 大多数情况下,它将授予 (true),但如果它是 deny (false),则权限实际上被阻止了。spring-doc.cadn.net.cn

Spring Security 不提供任何特殊的集成来自动创建、更新或删除 ACL 作为 DAO 或存储库作的一部分。 相反,您需要为各个域对象编写如上所示的代码。 值得考虑在服务层上使用 AOP 以自动将 ACL 信息与服务层作集成。 我们过去发现这是一种非常有效的方法。spring-doc.cadn.net.cn

使用上述技术在数据库中存储一些 ACL 信息后,下一步是实际使用 ACL 信息作为授权决策逻辑的一部分。 您在这里有多种选择。 你可以自己写AccessDecisionVoterAfterInvocationProvider分别在方法调用之前或之后触发。 此类类将使用AclService检索相关的 ACL,然后调用Acl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)以决定是授予还是拒绝权限。 或者,您可以使用我们的AclEntryVoter,AclEntryAfterInvocationProviderAclEntryAfterInvocationCollectionFilteringProvider类。 所有这些类都提供了一种基于声明的方法,用于在运行时评估 ACL 信息,从而无需编写任何代码。 请参阅示例应用程序以了解如何使用这些类。spring-doc.cadn.net.cn