这个问题似乎暗示了对 Spring ACL 的轻微误解。在问题中,我们被问到如何为String
对象注册 ACL,以便此方法保护起作用:
@PreAuthorize("hasPermission(#someInput, 'READ')")
public boolean myMethod(String someInput) {
return true;
}
正如这个答案String
中提到的,保护一个对象并没有什么意义。当您考虑它时,这是有道理的。松散地说,我们可以将所有对象分为两类:
- 持久化到数据库的对象——我们称它们为实体
- 未持久化到数据库的对象 - 我们称它们为瞬态
在我能想到的任何现实生活用例中,只有保护对实体的访问才有意义,而不是暂时的;我稍后会为这个案例进行一般性的辩论。不过,首先,让我们坚持一个与问题相关的用例,看看为什么在这种情况下保护瞬态可能不是所需要的。
用例
目前还不是 100% 清楚问题中的用例是什么,例如someInput
代表什么。但我假设用例类似于以下内容。假设有BankAccount
实体和对这些实体的一些操作,例如readAccount
。只有对 a 具有读取权限的用户BankAccount
才能调用readAccount
. 现在,BankAccount
实体可以通过它们accountNumber
的类型来唯一标识String
。所以我们可能会被错误地引导到尝试这样的事情,这类似于问题中的代码:
@PreAuthorize("hasPermission(#accountNumber, 'READ')")
public Account readAccount(String accountNumber) {
//CODE THAT RETRIEVES THE ENTITY FROM THE DB AND RETURNS IT
}
好吧,这不是一个糟糕的假设。我想在这个阶段我们的想法是 Spring ACL 存储一个帐号表,并且对于每个帐号,都有一个具有 READ 访问权限的人员的列表。问题是,Spring ACL 不能那样工作。如this answer中所述,Spring ACL 通过以下方式识别对象:
- 对象的类别是什么?在这种情况下,它将是
java.lang.String
- 对象的 ID 是什么?在这种情况下,Spring ACL 要求对象需要一个
getId()
方法。幸运的是,如果您使用的是 Hibernate,则默认情况下,您的所有实体都将具有此功能,因此您无需执行任何额外操作即可实现它。但是字符串呢?好吧,这没有getId()
方法。因此 Spring ACL 将无法为其注册 ACL,并且您将无法为 Strings 设置任何权限。
仔细想想,Spring ACL 是这样设计的。该getId()
方法允许我们将持久化的 ACL 权限条目与持久化的实体相关联。这是典型的用例。所以在上面的例子中,我们真正想做的是限制对Account
对象的访问,而不是帐号。在这种情况下,我们有两个选择:预授权或后授权。
使用预授权,我们需要完全限定的Account
类路径。因此,假设它在 packageX.Y
中,我们将拥有:
@PreAuthorize("hasPermission(#accountId, 'X.Y.Account', 'READ')")
public Account readAccount(Long accountId) {
//CODE THAT RETRIEVES THE ENTITY FROM THE DB AND RETURNS IT
}
请注意,在上面,我们使用 ID 而不是帐号来识别帐户。这是允许您使用 Spring ACL 识别实体的唯一方法,因为这getId()
是 ACL 与其关联对象之间的链接。当然,您可以自由编写任何您喜欢的代码来通过给定的 ID 检索对象,例如,您可以做一些愚蠢的事情,例如在检索之前增加 ID。因此,不能保证返回的对象与在这种情况下授权的对象相同:这取决于您编写正确的检索代码。
我们可以保护该方法的另一种方法是使用后授权,如下所示:
@PostAuthorize("hasPermission(returnObject, 'READ')")
public Account readAccount(String accountNumber) {
//CODE THAT RETRIEVES THE ENTITY FROM THE DB AND RETURNS IT
}
在这种情况下,实际调用了检索帐户实体的代码,然后只有在检索到它之后,ACL 框架才会根据当前用户和 READ 权限检查作为返回对象的帐户。这里的一个优点是我们可以以任何我们喜欢的方式检索帐户,例如accountNumber
在这种情况下。另一个优点是保证被授权的对象与返回的对象相同。缺点是我们必须实际进行检索,然后才能调用用户是否有权限。如果他们没有权限,那么检索到的对象就会被有效地丢弃,因此它的性能可能比@PreAuthorize
检索代码运行成本高一些。
为什么用 Spring ACL 保护字符串无论如何都没有意义
从技术上讲,我想你可能能够保护字符串,或者实际上任何其他瞬态,只要它有一个getId()
方法。例如,使用字符串,我们可以添加一个扩展函数getId()
。但我想不出一个实际用例来解释我们为什么要这样做。例如,假设我们不仅有Account
对象,而且我们也有Customer
对象。假设Customer
对象可以由一个customerNumber
字段唯一标识,该字段是String
. 假设我们想以与帐户类似的方式限制对客户的访问。那么如果巧合 acustomerNumber
匹配 anaccountNumber
呢?根据Spring 文档,在 Spring ACL 中,对于对象类和 ID 的每种组合,我们只允许在对象标识表中输入一个条目:
CONSTRAINT uk_acl_object_identity UNIQUE (object_id_class, object_id_identity)
因此,假设String
"fadfads389"
恰好是 a customerNumber
for someCustomer
和 an accountNumber
for some Account
。如果我们通过 Spring ACL 限制对它的访问,那意味着什么?这是否意味着用户可以访问该帐户?客户?两个都?
希望这个例子能说明为什么当使用 Spring ACL 来识别实体时保护一些瞬态类没有意义String
:当我们对保护实体感兴趣时,我们只需使用隐式 ID 保护实体本身这些实体的名称,例如 Hibernate 存储的 ID。
瞬态仍然可以受到保护
当然,没有什么能阻止您将对象标识条目添加到acl_object_identity
您想要的任何类的 Spring ACL 表中,只要该类具有getId()
方法即可。因此,您当然可以添加与瞬态相关的权限,如果这些瞬态再次出现在内存中,那么 Spring ACL 将启动。但这并不是 Spring ACL 的真正设计目的 - 它实际上是为了保护实体,而不是瞬态,通过 链接到 ACL 逻辑getId()
。
字符串仍然可以在 PreAuthorize 中使用
现在,虽然我们不应该真正使用Spring ACL来保护String
s,但这并不是说@PreAuthorize
在Strings
. 我们注意到它@PreAuthorize
可以处理任何 SpEL表达式,与此处@PostAuthorize
指出的 for 和其他方法注释相同。因此,例如,您可以执行以下操作:
@PreAuthorize("#user.accountNumbers.contains(#accountNumber)")
public Account readAccount(String accountNumber, User user) {
//CODE THAT RETRIEVES THE ENTITY FROM THE DB AND RETURNS IT
}
以上假设User
该类维护该用户有权访问的帐号列表,因此推测 aUser
是一个实体,或者至少由一些数据库持久化数据支持。
但是,如果您确实想走这条路,请当心。您不仅冒着将访问控制逻辑与其他业务逻辑纠缠在一起的风险,而且还可能在性能方面有所损失;Spring ACL 使用缓存来快速查找权限,而上面的代码可能需要User
从数据库中获取数据才能进行授权。