如何在 Spring Security 3.1 中使用 acl_entry 表中的掩码字段?

Posted

技术标签:

【中文标题】如何在 Spring Security 3.1 中使用 acl_entry 表中的掩码字段?【英文标题】:How do i use the mask field in acl_entry table in Spring Security 3.1? 【发布时间】:2012-02-19 13:19:14 【问题描述】:

我使用 Spring Security 3.1 ACL 实现。因此,根据教程,我创建了一个带有下表的 acl 数据库:

CREATE TABLE IF NOT EXISTS `acl_class` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `class` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_uk_2` (`class`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `acl_entry` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `acl_object_identity` bigint(20) NOT NULL,
  `ace_order` int(11) NOT NULL,
  `sid` bigint(20) NOT NULL,
  `mask` int(11) NOT NULL,
  `granting` tinyint(1) NOT NULL,
  `audit_success` tinyint(1) NOT NULL,
  `audit_failure` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_uk_4` (`acl_object_identity`,`ace_order`),
  KEY `foreign_fk_5` (`sid`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `acl_object_identity` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `object_id_class` bigint(20) NOT NULL,
  `object_id_identity` bigint(20) NOT NULL,
  `parent_object` bigint(20) DEFAULT NULL,
  `owner_sid` bigint(20) DEFAULT NULL,
  `entries_inheriting` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_uk_3` (`object_id_class`,`object_id_identity`),
  KEY `foreign_fk_1` (`parent_object`),
  KEY `foreign_fk_3` (`owner_sid`)
) ENGINE=InnoDB;

CREATE TABLE IF NOT EXISTS `acl_sid` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `principal` tinyint(1) NOT NULL,
  `sid` varchar(100) NOT NULL,
  `password` varchar(255) NOT NULL,
  `salt` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

这适用于 Antotations,例如:

@PreAuthorize("hasPermission(#element, 'WRITE')")
@PostAuthorize("hasPermission(returnObject, 'READ')")

在表 acl_entry 中为字段掩码设置了“读取”和“写入”权限。据我了解,1 表示“读取”,2 表示“写入”,4 表示“创建”,8 表示“删除”,16 表示“管理”,因为它似乎是一种按位身份验证方法。

    问题:我是否正确理解了权利的授予? 问题:如何指定“读/写”等组合权限?我可以“设置”位 0(即 int 1)和 1(即 int 2)所以我得到掩码值 1+2=3?

现在我必须为“READ”和“Write”权限创建单个条目,这不太方便。

【问题讨论】:

【参考方案1】:

This answer 指出 Spring 会比较 ACE 掩码和我们正在检查的权限之间的精确匹配。正如 OP 所说:

现在我必须为“READ”和“Write”创建单个条目 许可,这不是很方便。

-是的。这似乎是默认行为。正如我们从here 提供的任何数据库模式中看到的那样,我们可以为给定对象添加多个条目。因此,如果我们想授予 Alice 读写访问权限,我们会添加一个读取条目,可能顺序为 1,然后添加一个写入条目,顺序为 2 或其他。

虽然默认行为似乎如上,但文档中提到的“按位”可能会让新手感到困惑。但是,我想对于一个已经存在了很长时间并被很多人依赖的框架的向后兼容性,改变默认行为可能为时已晚。 Spring 开发人员似乎很清楚这个难题,因此他们提供了一种足够简单的方法来克服它。正如this answer 指出的那样,关键是提供自定义PermissionGrantingStrategy。链接的答案显示了如何通过 XML 在类中进行挂钩。不过,我对基于代码的配置更满意,所以我关注了this link,它解释了如何在代码中设置 Spring ACL。

现在,您所要做的与that link 中描述的不同,就是替换这个 Bean 定义:

@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() 
    return new DefaultPermissionGrantingStrategy(
      new ConsoleAuditLogger());

而是返回您自己制定的权限授予策略。不过不用担心!您不必完全从头开始编写自己的课程。然后,您的自定义权限授予策略可以只是子类型类DefaultPermissionGrantingStrategy 并仅覆盖方法isGranted。默认实现确实只是完全匹配:

protected boolean isGranted(AccessControlEntry ace, Permission p) 
    return ace.getPermission().getMask() == p.getMask();

但作者似乎很清楚需要覆盖,基于方法文档中的这个 sn-p:

默认情况下,我们会比较权限掩码以进行完全匹配。 此策略的子类可以覆盖此行为并实现 更复杂的比较,例如ACE 的逐位比较 授予访问权限。

文档更进一步,实际上为您提供了执行覆盖的代码。我没有对此进行测试,但我相信它只是:

protected boolean isGranted(AccessControlEntry ace, Permission p) 
        if (ace.isGranting() && p.getMask() != 0) 
            return (ace.getPermission().getMask() & p.getMask()) != 0;
          else 
            return ace.getPermission().getMask() == p.getMask();
         

你应该使用按位 ACL 吗?

在覆盖 Spring ACL 的默认值以使用按位存储之前,您应该首先问自己是否值得付出额外的努力以及偏离 OOTB 行为带来的错误风险。

而且,您应该意识到您将失去一些在非按位 Spring ACL 中可用的行为:即删除 ACE 的能力。我将用一个例子来演示。假设有一个名为CCompany 对象和一个名为DDepartment 对象,它嵌套在Spring ACL 层次结构中的C 之下。并假设DC 继承其权限。

现在假设 Alice 对整个公司 C 具有 READ 访问权限。由此,她继承了C 下所有部门的读取权限,包括D。有一天,Bob 决定只授予 Alice 对部门 D 的 WRITE 访问权限,而不是任何其他部门。使用按位逻辑,为D 添加了什么 ACE?由于单个 ACE 必须包含所有权限 READ、WRITE 等编码到各个位中,因此 READ 位必须为 0 或 1。两者都可能存在问题:

    假设新 ACE 上的 READ 位为 0。假设 Alice 现在被拒绝对D 的 READ 访问是很疯狂的,所以我们不得不假设 0 位并不意味着“拒绝”,而是较弱的意思是“未授予”。这意味着 ACL 逻辑仍将查看父对象以授予权限。这意味着我们无法拒绝覆盖在层次结构中更高级别授予的权限。 另一方面,我们可以实现更复杂的逻辑,通过父对象C 检测 Alice 具有 READ 访问权限,因此在新 ACE 上将 READ 位设置为 1。但是,如果我们删除 Alice 对C 的 READ 访问权限会发生什么?没有办法知道对 D 的 READ 访问也应该被删除,除非我们将该信息存储在某个地方,这样做会破坏按位 ACL 的全部意义,即压缩存储。

解决此问题的一种方法是为每个权限使用 两个 位,而不是 1。例如,您可以解释如下: 00 表示未授予或拒绝 - 向父母寻求值, 01 表示拒绝,10 表示同意。在这种情况下,11 将是免费的,但您可以稍后将其用于其他用途,例如。但是,使用 2 位会更复杂,因为 OOTB 权限只有 1 位,因此您必须自己编写。尽管付出了所有努力,最终您只能获得一半的存储压缩。

【讨论】:

【参考方案2】:

为了实现逐位权限评估,而不是实现 PermissionEvaluator,这可能非常困难,您可以用自己的重写 DefaultPermissionGrantingStrategy。

这可能是您的 Spring ACL 配置

1) 您的 ACL 服务

<bean class="org.springframework.security.acls.jdbc.JdbcMutableAclService" id="aclService">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="lookupStrategy"/>
<constructor-arg ref="aclCache"/>
</bean>

2) 你的查找策略(dataSource、aclCache 和 aclAuthorizationStrategy 是默认的)

<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource"/>
<constructor-arg ref="aclCache"/>
<constructor-arg ref="aclAuthorizationStrategy"/>
<constructor-arg ref="permissionGrantingStrategy"/>
</bean>

3) 有趣的部分来了,PermissionGrantingStrategy (http://docs.spring.io/spring-security/site/docs/current/apidocs/org/springframework/security/acls/model/PermissionGrantingStrategy.html)

<bean id="permissionGrantingStrategy" class="com.example.MyPermissionGrantingStrategy"/>

在这里您将实现逐位权限逻辑,然后覆盖 Spring 的默认设置。

希望对你有帮助

【讨论】:

仅供参考。 Spring 文档链接已损坏【参考方案3】:

根据Spring Security 3.1 by PacktPub:

很遗憾,AclImpl的实际实现直接比较了我们的[@PostFilter]注解中SpEL表达式指定的权限,和数据库中ACE存储的权限,没有使用按位逻辑强>。 Spring Security 社区正在争论这是无意还是按预期工作。 . .

那本书中的示例试图完全按照您的描述进行操作——它指定了一个角色为 3 的用户,用于 read/write,但用户read 拒绝访问权限掩码为 1 的对象。

解决方案是编写自己的自定义权限评估器。

MyPermissionEvaluator.java

public class MyPermissionEvaluator implements PermissionEvaluator 

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object requiredPermissions) 
        //some way to access your user's assigned permission mask
        int permissionMask = MyUserDetails.getMask();

        //the requiredPermissions object must be cast as a String, and then
        //converted to an integer, even though it is an integer in the ACL table
        int permissionsRequired = Integer.valueOf(requiredPermissions.toString());

        //a simple bitwise OR will identify whether the user has the required permissions
        return ((permissionMask | permissionsRequired) == permissionMask);
    

    . . .


要实际使用此自定义权限评估器,请编辑您的 security.xml 文件:

<security:global-method-security pre-post-annotations="enabled">
    <security:expression-handler ref="expressionHandler"/>
</security:global-method-security>

<bean id="espressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
    <property name="permissionEvaluator" ref="permissionEvaluator"/>
</bean>

<bean id="permissionEvaluator" class="my.project.package.MyPermissionEvaluator"/>

最后,每当方法或类需要一定的权限级别时:

@PreAuthorize("hasPermission(#this, '4')")
public void mySecuredMethod()  //some secured method

现在,您可以将 ACL 表中的权限掩码设置为与您的组织需求(按位)相对应的任何内容,并以您识别每个用户权限的任何方式执行相同操作。例如,

user    site_admin_bit    database_admin_bit    edit_data_bit    write_data_bit    read_data_bit
nancy        0                   1                   1                 0                1

因此,Nancy 的权限掩码为 13(在可能的 31 中),存储在您的用户详细信息实现中。如果她尝试访问具有edit_data 权限要求的对象,她的权限将根据掩码要求 4 进行检查,并且按位 OR 评估 (permissionMask | permissionsRequired == permissionMask) 将评估为 @987654331 @。

据我估计,这是实现特定于组织的按位权限掩码的最简单方法(可以使用 32 位,我认为应该足够了)。根据参考书,Spring注解中使用的hasPermissionSpEL表达式将用户的权限作为一个完整的单元进行评估;如果用户在 3 处设置了 读/写权限,但注释仅针对 read (1) 进行评估,则用户将拒绝访问。

【讨论】:

很确定这需要是按位而不是按位或。所以应该是return ((permissionMask &amp; permissionsRequired) == permissionMask); 仅供参考。第一个链接现在是 404【参考方案4】:

来自SpringSecurity

如上一段所述,ACL 系统使用整数位 掩蔽。不用担心,您不必注意位的细节 转移到使用 ACL 系统,但足以说我们有 32 我们可以打开或关闭位。这些位中的每一个代表一个 权限,默认情况下,权限是读取(位 0),写入 (位 1)、创建(位 2)、删除(位 3)和管理(位 4)。它是 如果您想使用,易于实现您自己的 Permission 实例 其他权限,ACL 框架的其余部分将运行 不知道你的扩展。

@question#1:是的,没错。

@question#2:你可以使用类似的东西: new BasePermission(BasePermission.WRITE.getMask() | BasePermission.READ.getMask()) 获得READWRITE 权限。

来自春季文档:

// 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;
Permission p = new BasePermission(BasePermission.WRITE.getMask() | BasePermission.READ.getMask());    

// 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);

【讨论】:

好的,新的 BasePermission 是一个解决方案,但我需要数据库表 acl_entry 中的 2 个条目。我想在数据库中保存一个掩码值 3,因此 Spring Security 将为 @PreAuthorize("hasPermission(#element, 'WRITE')") 和 @PostAuthorize("hasPermission(returnObject, 'READ')") 提供 true .这可能吗? 我添加了一个从 spring 文档复制的代码示例。它将完全满足您的需求。查看代码示例中的Permission 对象。您应该在数据库中恰好有 1 行用于读取/写入权限。 我已经尝试了新的 BasePermission。如果我想检查“READ/WRITE”,但不是像我预期的那样检查单个权限,它工作正常。所以 @PreAuthorize("hasPermission(#element, 'READ/WRITE')") 是真的,但 @PreAuthorize("hasPermission(#element, 'WRITE')") 是假的。所以这不是我真正想做的,但仍然有帮助。谢谢

以上是关于如何在 Spring Security 3.1 中使用 acl_entry 表中的掩码字段?的主要内容,如果未能解决你的问题,请参考以下文章

spring 3.1 with hibernate 4 with spring security 3.1:如何确保包含所有依赖项以及要包含哪些标签?

如何使用 Spring Security 3.1 以编程方式登录用户

Spring Security 3.1 xsd 和 jars 不匹配问题

Spring security:在 3.1 中,仅针对“GET”请求绕过安全过滤器

spring security 3.1 实现权限控制

在 Spring Security 3.1 中,StandardPasswordEncoder 对密码进行盐渍化最合适的用途是啥?