对于最新的稳定版本,请使用 Spring Security 6.4.1! |
域对象安全性 (ACL)
概述
复杂的应用程序通常会发现需要定义访问权限,而不仅仅是在 Web 请求或方法调用级别。
相反,安全决策需要包括 who ()、where () 和 what () 。
换句话说,授权决策还需要考虑方法调用的实际域对象实例主题。Authentication
MethodInvocation
SomeDomainObject
假设您正在为宠物诊所设计应用程序。 基于 Spring 的应用程序将有两组主要用户:宠物诊所的工作人员以及宠物诊所的客户。 员工将可以访问所有数据,而您的客户将只能看到他们自己的客户记录。 为了更有趣,您的客户可以允许其他用户查看他们的客户记录,例如他们的 “puppy preschool” 导师或他们当地的 “Pony Club” 的主席。 使用 Spring Security 作为基础,您可以使用多种方法:
-
编写您的业务方法来实施安全性。 您可以查阅域对象实例中的集合,以确定哪些用户具有访问权限。 通过使用 ,您将能够访问该对象。
Customer
SecurityContextHolder.getContext().getAuthentication()
Authentication
-
编写 an 以从对象中存储的 s 强制实施安全性。 这意味着您需要使用自定义 s 填充 ,这些自定义表示委托人有权访问的每个域对象实例。
AccessDecisionVoter
GrantedAuthority[]
Authentication
AuthenticationManager
Authentication
GrantedAuthority[]
Customer
-
编写 an 以强制实施安全性并直接打开目标域对象。 这意味着您的选民需要访问允许其检索对象的 DAO。 然后,它将访问对象的已批准用户集合并做出适当的决定。
AccessDecisionVoter
Customer
Customer
Customer
这些方法中的每一种都是完全合法的。
但是,第一个选项将您的授权检查与您的业务代码耦合在一起。
这样做的主要问题包括单元测试的难度增加,以及在其他位置重用授权逻辑会更加困难的事实。
从对象获取 s 也很好,但不会缩放到大量 s。
如果用户可能能够访问 5,000 秒(在这种情况下不太可能,但想象一下,如果它是大型 Pony Club 的流行兽医!),构建对象所需的内存量和时间将是不可取的。
最后一种方法(直接从外部代码打开 the)可能是三种方法中最好的。
它实现了关注点分离,并且不会滥用内存或 CPU 周期,但它仍然效率低下,因为 和最终的业务方法本身都会对负责检索对象的 DAO 执行调用。
每个方法调用两次访问显然是不可取的。
此外,对于列出的每种方法,您需要从头开始编写自己的访问控制列表 (ACL) 持久性和业务逻辑。Customer
GrantedAuthority[]
Authentication
Customer
Customer
Authentication
Customer
AccessDecisionVoter
Customer
幸运的是,还有另一种选择,我们将在下面讨论。
关键概念
Spring Security 的 ACL 服务以 .
您需要将此 JAR 添加到 Classpath 中,以使用 Spring Security 的域对象实例安全功能。spring-security-acl-xxx.jar
Spring Security 的域对象实例安全功能以访问控制列表 (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 的文本表示形式和一个标志,用于指示文本表示形式是引用主体名称还是 . 因此,每个唯一主体 or 都有一行。 在接收权限的上下文中使用时,SID 通常称为“接收者”。
GrantedAuthority
GrantedAuthority
-
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 位)。
如果您希望使用其他权限,则可以轻松实现自己的实例,并且 ACL 框架的其余部分将在不知道您的扩展的情况下运行。Permission
重要的是要了解系统中域对象的数量与我们选择使用 integer bit masking 的事实完全无关。 虽然您有 32 位可用于权限,但您可能有数十亿个域对象实例(这意味着ACL_OBJECT_IDENTITY中有数十亿行,很可能是 ACL_ENTRY)。 我们之所以提出这一点,是因为我们发现有时人们错误地认为他们需要为每个潜在的 domain object 提供一点,但事实并非如此。
现在我们已经提供了 ACL 系统功能的基本概述,以及它在表结构中的外观,让我们探索一下关键接口。 主要接口包括:
-
Acl
:每个域对象都有且只有一个对象,该对象在内部包含 s 并知道 的所有者。 Acl 不直接引用域对象,而是引用 . 存储在 ACL_OBJECT_IDENTITY 表中。Acl
AccessControlEntry
Acl
ObjectIdentity
Acl
-
AccessControlEntry
: An 包含多个 s,在框架中通常缩写为 ACE。 每个 ACE 都引用 , 和 的特定元组。 ACE 也可以是授予或不授予的,并包含审核设置。 ACE 存储在 ACL_ENTRY 表中。Acl
AccessControlEntry
Permission
Sid
Acl
-
Permission
:权限表示特定的不可变位掩码,并为位掩码和输出信息提供便捷功能。 上面显示的基本权限(位 0 到 4)包含在类中。BasePermission
-
Sid
:ACL 模块需要引用 principals 和 s。 接口提供间接级别,它是“security identity”的缩写。 常见类包括 (表示对象内的主体) 和 . 安全身份信息存储在 ACL_SID 表中。GrantedAuthority[]
Sid
PrincipalSid
Authentication
GrantedAuthoritySid
-
ObjectIdentity
:每个域对象在 ACL 模块内部都由 . 默认实现称为 。ObjectIdentity
ObjectIdentityImpl
-
AclService
:检索给定 的 适用 。 在包含的实现 () 中,检索操作被委托给 . 它提供了一种高度优化的策略,用于检索 ACL 信息,使用批量检索 () 并支持利用具体化视图、分层查询和类似的以性能为中心的非 ANSI SQL 功能的自定义实现。Acl
ObjectIdentity
JdbcAclService
LookupStrategy
LookupStrategy
BasicLookupStrategy
-
MutableAclService
:允许显示已修改的 for persistence。 如果您不想,则不必使用此界面。Acl
请注意,我们开箱即用的 AclService 和相关数据库类都使用 ANSI SQL。 因此,这应该适用于所有主要数据库。 在撰写本文时,该系统已经使用 Hypersonic SQL、PostgreSQL、Microsoft SQL Server 和 Oracle 成功进行了测试。
Spring Security 附带了两个示例,用于演示 ACL 模块。 第一个是联系人示例,另一个是文档管理系统 (DMS) 示例。 我们建议查看这些示例。
开始
要开始使用 Spring Security 的 ACL 功能,您需要将 ACL 信息存储在某个位置。
这需要实例化 using Spring。
然后,将 the 注入到 and 实例中。
后者提供高性能的 ACL 检索能力,前者提供 mutator 能力。
有关示例配置,请参阅 Spring Security 附带的示例之一。
您还需要使用上一节中列出的四个特定于 ACL 的表填充数据库(有关相应的 SQL 语句,请参阅 ACL 示例)。DataSource
DataSource
JdbcMutableAclService
BasicLookupStrategy
创建所需的模式并实例化后,接下来需要确保您的域模型支持与 Spring Security ACL 包的互操作性。
希望证明这就足够了,因为它提供了大量的使用方式。
大多数人都会拥有包含方法的域对象。
如果返回类型是 long 或与 long 兼容(例如 int),您会发现无需进一步考虑问题。
ACL 模块的许多部分都依赖于长标识符。
如果你没有使用 long(或 int、byte 等),你很可能需要重新实现一些 class。
我们不打算在 Spring Security 的 ACL 模块中支持非长标识符,因为 long 已经与所有数据库序列(最常见的标识符数据类型)兼容,并且具有足够的长度来适应所有常见的使用场景。JdbcMutableAclService
ObjectIdentityImpl
public Serializable getId()
ObjectIdentity
以下代码片段显示了如何创建 , 或修改现有 :Acl
Acl
-
Java
-
Kotlin
// 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 Security 不提供任何特殊的集成来自动创建、更新或删除 ACL 作为 DAO 或存储库操作的一部分。 相反,您需要为各个域对象编写如上所示的代码。 值得考虑在服务层上使用 AOP 以自动将 ACL 信息与服务层操作集成。 我们过去发现这是一种非常有效的方法。
使用上述技术在数据库中存储一些 ACL 信息后,下一步是实际使用 ACL 信息作为授权决策逻辑的一部分。
您在这里有多种选择。
您可以编写自己的 OR 版本,分别在方法调用之前或之后触发。
此类将用于检索相关的 ACL,然后调用以确定是授予还是拒绝权限。
或者,你可以使用我们的 、 或 类。
所有这些类都提供了一种基于声明的方法,用于在运行时评估 ACL 信息,从而无需编写任何代码。
请参阅示例应用程序以了解如何使用这些类。AccessDecisionVoter
AfterInvocationProvider
AclService
Acl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)
AclEntryVoter
AclEntryAfterInvocationProvider
AclEntryAfterInvocationCollectionFilteringProvider