此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

本节介绍Spring Security如何通过访问控制列表(ACL)提供域对象安全性。Spring中文文档

复杂的应用程序通常需要定义超出 Web 请求或方法调用级别的访问权限。 相反,安全决策需要包括 who ()、where () 和 what ()。 换言之,授权决策还需要考虑方法调用的实际域对象实例主体。AuthenticationMethodInvocationSomeDomainObjectSpring中文文档

想象一下,您正在为一家宠物诊所设计一个应用程序。 基于 Spring 的应用程序主要有两组用户:宠物诊所的员工和宠物诊所的客户。 员工应该有权访问所有数据,而您的客户应该只能查看他们自己的客户记录。 为了让它更有趣,您的客户可以让其他用户查看他们的客户记录,例如他们的“小狗学龄前”导师或当地“小马俱乐部”的主席。 当您使用 Spring Security 作为基础时,您有几种可能的方法:Spring中文文档

  • 编写业务方法以强制实施安全性。 可以查阅域对象实例中的集合,以确定哪些用户具有访问权限。 通过使用 ,您可以访问该对象。CustomerSecurityContextHolder.getContext().getAuthentication()AuthenticationSpring中文文档

  • 编写 an 以强制执行对象中存储的实例的安全性。 这意味着您需要使用自定义对象填充 来表示主体有权访问的每个域对象实例。AccessDecisionVoterGrantedAuthority[]AuthenticationAuthenticationManagerAuthenticationGrantedAuthority[]CustomerSpring中文文档

  • 编写 an 以强制实施安全性并直接打开目标域对象。 这意味着您的选民需要访问 DAO 以使其检索对象。 然后,它可以访问对象的已批准用户集合并做出适当的决定。AccessDecisionVoterCustomerCustomerCustomerSpring中文文档

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

幸运的是,还有另一种选择,我们稍后会讨论。Spring中文文档

关键概念

Spring Security 的 ACL 服务在 . 您需要将此 JAR 添加到类路径中才能使用 Spring Security 的域对象实例安全功能。spring-security-acl-xxx.jarSpring中文文档

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

  • 一种高效检索所有域对象的 ACL 条目(并修改这些 ACL)的方法Spring中文文档

  • 一种确保在调用方法之前允许给定主体使用对象的方法Spring中文文档

  • 一种确保给定主体在调用方法后允许使用对象(或它们返回的内容)的方法Spring中文文档

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

鉴于数据库是 ACL 模块操作的核心,我们需要探索实现中默认使用的四个主表。 这些表在典型的Spring Security ACL部署中按大小顺序显示,最后列出行数最多的表:Spring中文文档

  • ACL_SID允许我们唯一标识系统中的任何主体或机构(“SID”代表“安全 IDentity”)。 唯一的列是 ID、SID 的文本表示形式,以及指示文本表示形式是引用主体名称还是 . 因此,每个唯一主体或 . 在接收权限的上下文中使用时,SID 通常称为“收件人”。GrantedAuthorityGrantedAuthoritySpring中文文档

  • ACL_CLASS让我们唯一标识系统中的任何域对象类。 唯一的列是 ID 和 Java 类名。 因此,对于我们希望存储其 ACL 权限的每个唯一类,都有一行。Spring中文文档

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

  • 最后,存储分配给每个收件人的个人权限。 列包括 、收件人的外键(即ACL_SID的外键)、我们是否进行审核,以及表示授予或拒绝的实际权限的整数位掩码。 对于每个接收到使用域对象的权限的收件人,我们都有一行。ACL_ENTRYACL_OBJECT_IDENTITYSpring中文文档

如上一段所述,ACL 系统使用整数位掩码。 但是,您无需了解位移的细节即可使用 ACL 系统。 我只想说我们有 32 位可以打开或关闭。 这些位中的每一个都表示一个权限。默认情况下,权限为读取(位 0)、写入(位 1)、创建(位 2)、删除(位 3)和管理(位 4)。 如果您希望使用其他权限,您可以实现自己的实例,ACL 框架的其余部分无需了解您的扩展即可运行。PermissionSpring中文文档

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

现在我们已经提供了 ACL 系统功能的基本概述,以及它在表结构级别的样子,我们需要探索关键接口:Spring中文文档

  • Acl:每个域对象都有一个且只有一个对象,该对象在内部保存对象并知道 的所有者。 Acl 不直接引用域对象,而是引用 . 存储在表中。AclAccessControlEntryAclObjectIdentityAclACL_OBJECT_IDENTITYSpring中文文档

  • AccessControlEntry:一个包含多个对象,这些对象在框架中通常缩写为 ACE。 每个 ACE 都是指 、 和 的特定元组。 ACE 也可以是授予或不授予,并包含审核设置。 ACE 存储在表中。AclAccessControlEntryPermissionSidAclACL_ENTRYSpring中文文档

  • Permission:权限表示特定的不可变位掩码,并为位掩码和输出信息提供便利的功能。 上面介绍的基本权限(位 0 到 4)包含在类中。BasePermissionSpring中文文档

  • Sid:ACL 模块需要引用委托人和实例。 接口提供间接级别。(“SID”是“Security IDentity”的缩写。 常见的类包括 (表示对象内部的主体) 和 . 安全标识信息存储在表中。GrantedAuthority[]SidPrincipalSidAuthenticationGrantedAuthoritySidACL_SIDSpring中文文档

  • ObjectIdentity:每个域对象在 ACL 模块内部都由 . 默认实现称为 。ObjectIdentityObjectIdentityImplSpring中文文档

  • AclService:检索适用于给定 . 在包含的实现 () 中,检索操作被委托给 . 它提供了一种高度优化的策略,用于检索 ACL 信息、使用批处理检索 () 并支持使用具体化视图、分层查询和类似的以性能为中心的非 ANSI SQL 功能的自定义实现。AclObjectIdentityJdbcAclServiceLookupStrategyLookupStrategyBasicLookupStrategySpring中文文档

  • MutableAclService:为了持久化,呈现修改后的效果。 此接口的使用是可选的。AclSpring中文文档

请注意,我们的数据库类和相关数据库类都使用 ANSI SQL。 因此,这应该适用于所有主要数据库。 在撰写本文时,该系统已成功通过Hypersonic SQL,PostgreSQL,Microsoft SQL Server和Oracle的测试。AclServiceSpring中文文档

Spring Security 附带了两个示例,用于演示 ACL 模块。 第一个是联系人示例,另一个是文档管理系统 (DMS) 示例。 我们建议您看一下这些示例。Spring中文文档

开始

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

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

以下代码片段显示了如何创建或修改现有的:AclAclSpring中文文档

// 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 的域对象关联的 ACL。 然后,我们添加一个 ACE,以便名为“Samantha”的主体可以“管理”对象。 代码片段是相对不言自明的,除了方法。 该方法的第一个参数确定插入新条目的 Acl 中的位置。 在前面的示例中,我们将新的 ACE 放在现有 ACE 的末尾。 最后一个参数是一个布尔值,指示 ACE 是授予还是拒绝。 大多数情况下,它授予 ()。但是,如果它拒绝 (),则权限实际上被阻止。FooinsertAceinsertAcetruefalseSpring中文文档

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

使用此处描述的技术将某些 ACL 信息存储在数据库中后,下一步就是实际使用 ACL 信息作为授权决策逻辑的一部分。 您在这里有很多选择。 您可以在方法调用之前或之后编写自己的或(分别)触发。 此类类将用于检索相关的 ACL,然后调用以决定是授予还是拒绝权限。 或者,您可以使用我们的 或类。 所有这些类都提供了一种基于声明的方法来在运行时评估 ACL 信息,使您无需编写任何代码。AccessDecisionVoterAfterInvocationProviderAclServiceAcl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)AclEntryVoterAclEntryAfterInvocationProviderAclEntryAfterInvocationCollectionFilteringProviderSpring中文文档

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