对于最新的稳定版本,请使用 Spring Security 6.3.3spring-doc.cn

对于最新的稳定版本,请使用 Spring Security 6.3.3spring-doc.cn

概述

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

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

  • 编写您的业务方法来实施安全性。 您可以查阅域对象实例中的集合,以确定哪些用户具有访问权限。 通过使用 ,您将能够访问该对象。CustomerSecurityContextHolder.getContext().getAuthentication()Authenticationspring-doc.cn

  • 编写 an 以从对象中存储的 s 强制实施安全性。 这意味着您需要使用自定义 s 填充 ,这些自定义表示委托人有权访问的每个域对象实例。AccessDecisionVoterGrantedAuthority[]AuthenticationAuthenticationManagerAuthenticationGrantedAuthority[]Customerspring-doc.cn

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

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

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

关键概念

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

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

  • 一种有效检索所有域对象的 ACL 条目(并修改这些 ACL)的方法spring-doc.cn

  • 一种确保在调用方法之前允许给定主体处理对象的方法spring-doc.cn

  • 一种确保在调用方法后允许给定主体处理您的对象(或它们返回的内容)的方法spring-doc.cn

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

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

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

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

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

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

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

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

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

  • Acl:每个域对象都有且只有一个对象,该对象在内部包含 s 并知道 的所有者。 Acl 不直接引用域对象,而是引用 . 存储在 ACL_OBJECT_IDENTITY 表中。AclAccessControlEntryAclObjectIdentityAclspring-doc.cn

  • AccessControlEntry: An 包含多个 s,在框架中通常缩写为 ACE。 每个 ACE 都引用 , 和 的特定元组。 ACE 也可以是授予或不授予的,并包含审核设置。 ACE 存储在 ACL_ENTRY 表中。AclAccessControlEntryPermissionSidAclspring-doc.cn

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

  • Sid:ACL 模块需要引用 principals 和 s。 接口提供间接级别,它是“security identity”的缩写。 常见类包括 (表示对象内的主体) 和 . 安全身份信息存储在 ACL_SID 表中。GrantedAuthority[]SidPrincipalSidAuthenticationGrantedAuthoritySidspring-doc.cn

  • ObjectIdentity:每个域对象在 ACL 模块内部都由 . 默认实现称为 。ObjectIdentityObjectIdentityImplspring-doc.cn

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

  • MutableAclService:允许显示已修改的 for persistence。 如果您不想,则不必使用此界面。Aclspring-doc.cn

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

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

开始

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

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

以下代码片段显示了如何创建 , 或修改现有 :AclAclspring-doc.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.cn

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

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