我正在尝试将Spring Security和ACL支持集成到现有的GWT应用程序中,如果我点击另一个无用的链接,我发誓将需要一个新的鼠标和键盘。我已经完成了使用Spring通过LDAP对Active Directory进行身份验证所需的工作,并且我已经弄清楚如何根据AD属性(即组成员身份)分配自定义权限,我甚至已经弄清楚了使用自定义ACL架构对权限(真正的位掩码操作)执行自定义检查。我还没想到的只是ACL表中的内容。
ACL_SID
id:bigint (pk)
principal:boolean (ak)
sid:varchar (ak)
这张表格非常明显;我们只在这里使用非主要条目。
ACL_CLASS
id:bigint (pk)
class:varchar (ak)
这张表也很明显。据我了解,我们只是为我们希望保护的每个类/接口创建一个条目。
ACL_ENTRY
id:bigint (pk)
acl_object_identity:bigint (fak)
ace_order:int (ak)
sid:bigint (fk)
mask:bigint
granting:boolean
audit_success:boolean
audit_failure:boolean
这张表也大多不言自明;我们已在mask
字段中使用bigint / long自定义架构,但问题源于acl_object_identity
引用的内容。显然,它指向ACL_OBJECT_IDENTITY
中的字段,但是......
ACL_OBJECT_IDENTITY
id:bigint (pk)
object_id_class:bigint (fak)
object_id_identity:bigint (ak)
parent_object_identity:bigint (fk)
owner_sid:bigint (fk)
entries_inheriting:boolean
object_id_identity
是什么?一个方法?它是如何通过Spring Security的注释引用的?
MyClass.java
@PreAuthorize("hasPermission(#someInput, 'READ')")
public boolean myMethod(String someInput) {
return true;
}
据推测,#someInput
某种方式指的是ACL_OBJECT_IDENTITY.object_id_identity
字段,但是如何?
答案 0 :(得分:0)
查看Grails Spring Security ACL Plugin Documentation。 它解释了用于特定插件的域类,但它可能对您有所帮助。查找AclObjectIdentity和AbstractAclObjectIdentity。
ACL_OBJECT_IDENTITY.object_id_identity字段是实例ID,唯一标识实例(与object_id_class一起)
在您的示例中,'someInput'将是该实例。 这段代码取自前面提到的文档:
@PreAuthorize("hasPermission(#report, delete) or hasPermission(#report, admin)")
void deleteReport(Report report) {
report.delete()
}
它解释了自己。如果当前经过身份验证的用户对给定的报表实例具有“admin”或“delete”权限,则会删除该报表。报告是代码段中的someInput。
希望这有帮助。
答案 1 :(得分:0)
首先,您需要某种域对象类来存储数据。该类必须具有getId()
方法。例如:
public class DomainObject {
private Long id;
private String data;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
将您的安全方法更改为:
@PreAuthorize("hasPermission(#someInput, 'READ')")
public boolean myMethod(DomainObject someInput) {
return true;
}
现在,someInput.getId()
与ACL_OBJECT_IDENTITY.object_id_identity
相同。
答案 2 :(得分:0)
这个问题似乎暗示了对 Spring ACL 的轻微误解。在问题中,我们被问到如何为 String
对象注册 ACL,以便此方法保护起作用:
@PreAuthorize("hasPermission(#someInput, 'READ')")
public boolean myMethod(String someInput) {
return true;
}
如 this answer 中所述,保护 String
对象实际上没有意义。当你想到它时,这是有道理的。粗略地说,我们可以将所有对象分为两类:
在我能想到的任何现实用例中,只有保护对实体的访问才有意义,而不是对瞬态的访问;稍后我将对此案进行一般性辩论。不过,首先,让我们坚持一个与问题相关的用例,看看为什么在这种情况下可能不需要保护瞬态。
问题中的用例不是 100% 清楚,例如someInput
代表什么。但我假设用例类似于以下内容。假设有 BankAccount
实体和对这些实体的一些操作,例如readAccount
。只有对 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
getId()
方法。幸运的是,如果您使用的是 Hibernate,默认情况下您的所有实体都将具有此功能,因此您无需执行任何额外操作即可实现它。但是字符串呢?嗯,这没有 getId()
方法。因此,Spring ACL 将无法为其注册 ACL,您也无法为字符串设置任何权限。仔细想想,Spring ACL 是这样设计的,这实际上是有道理的。 getId()
方法允许我们将持久化的 ACL 权限条目与持久化的实体相关联。这是典型的用例。所以在上面的例子中,我们真正想要做的是限制对 Account
对象的访问,而不是帐号。在这种情况下,我们有两种选择:预授权或后授权。
使用预授权,我们需要 Account
类的完全限定路径。所以假设它在包 X.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
稍差。
从技术上讲,我想您可能能够保护字符串,或者实际上任何其他瞬态,只要它具有 getId()
方法。例如,对于字符串,我们可以添加一个扩展函数 getId()
。但我想不出一个实际用例来说明我们为什么要这样做。例如,假设我们不仅有 Account
对象,而且还有 Customer
对象。假设 Customer
对象可通过 customerNumber
字段唯一标识,该字段是 String
。假设我们想以与帐户类似的方式限制对客户的访问。那么,如果一个 customerNumber
恰好匹配一个 accountNumber
呢?在 Spring ACL 中,对于对象类和 ID 的每个组合,我们只允许在对象标识表中使用一个条目,如Spring docs:
CONSTRAINT uk_acl_object_identity UNIQUE (object_id_class, object_id_identity)
因此,假设 String
"fadfads389"
恰好既是 customerNumber
的 Customer
,也是 accountNumber
的 Account
。如果我们通过 Spring ACL 限制对它的访问,那意味着什么?这是否意味着用户可以访问该帐户?客户?两个?
希望这个例子能说明为什么当瞬态类用于识别实体时,用 Spring ACL 保护一些瞬态类(例如 String
)没有真正意义:当我们对保护实体感兴趣时,我们只是保护实体本身,使用这些实体的隐式 ID,例如Hibernate 存储的 ID。
当然,没有什么可以阻止您将对象标识条目添加到 Spring ACL acl_object_identity
表中的任何您想要的类,只要该类具有 getId()
方法。因此,您当然可以添加与瞬态相关的权限,并且如果这些瞬态再次出现在内存中,那么 Spring ACL 就会启动。但这并不是 Spring ACL 的真正设计目的 - 它实际上是为了保护实体,而不是瞬态,通过 getId()
链接到 ACL 逻辑。
现在,虽然我们不应该真正使用 Spring ACL 来保护 String
,但这并不是说 @PreAuthorize
在涉及 { {1}}。我们注意到 Strings
可以处理 any SpEL 表达式,@PreAuthorize
和 here 指出的其他方法注释也是如此。因此,例如,您可以执行以下操作:
@PostAuthorize
上面假设 @PreAuthorize("#user.accountNumbers.contains(#accountNumber)")
public Account readAccount(String accountNumber, User user) {
//CODE THAT RETRIEVES THE ENTITY FROM THE DB AND RETURNS IT
}
类维护着一个用户有权访问的帐号列表,所以大概 User
是一个实体,或者至少由一些数据库持久化数据支持.
但是,如果您确实想走这条路,请注意。您不仅要冒着将访问控制逻辑与其他业务逻辑纠缠在一起的风险,还可能会在性能方面失去优势; Spring ACL 使用缓存来快速查找权限,而上述代码可能需要从数据库中获取 User
数据才能进行授权。