如何在spring security中解释hasPermission?

时间:2015-06-24 17:29:43

标签: java spring security spring-security spring-security-acl

我是春天安全的新手。我怎么解释这个?

Timestamp sqlDate = new Timestamp( System.currentTimeMillis() );
preparedStmt.setTimestamp( ++startIndex, sqlDate );

来自权限评估程序的哪个方法会被调用?我想在这种情况下会调用带有三个参数的那个。它正在检查当前用户是否对类型目标''opetussuunnitelma'具有'LUONTI'权限。我对吗?我们不能只包括“null”并且只传递两个参数。我读到第一个参数(Authentication对象)没有提供。

 @PreAuthorize("hasPermission(null, 'opetussuunnitelma', 'LUONTI')")
     OpetussuunnitelmaDto addOpetussuunnitelma(OpetussuunnitelmaDto opetussuunnitelmaDto);

2 个答案:

答案 0 :(得分:8)

  

来自权限评估程序的哪个方法会被调用?

public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) 

会被叫。

  

我读到第一个参数(Authentication对象)不是   提供。

它没有在您的注释中明确提供,而是由Spring隐式提供。您的注释应该只是阅读

@PreAuthorize("hasPermission(#opetussuunnitelmaDto, 'LUONTI')")

理想情况下,我会在执行授权之前检查他们是否经过身份验证。

@PreAuthorize("isAuthenticated() and hasPermission(#opetussuunnitelmaDto, 'LUONTI')")

更新您的评论

基本上,您可以使用以下任一方式调用PermissionEvaluator:

hasPermission('#targetDomainObject', 'permission')    // method1
hasPermission('targetId', 'targetType', 'permission') // method2

Spring始终提供身份验证。在您的情况下,您以下列方式调用hasPermission

  

hasPermission(null,' opetussuunnitelma',' LUONTI')")

哪个匹配 method2 ,但传入一个空ID并不合理,您要将哪个实体作为目标进行检查?根据您正在应用@PreAuthorize的方法,

  

OpetussuunnitelmaDto addOpetussuunnitelma(OpetussuunnitelmaDto   opetussuunnitelmaDto);

调用 method1 可能更有意义,因为您似乎有类似于目标域对象的内容。

答案 1 :(得分:0)

This answer 已经抓住了 OP 真正要问的问题的核心。我将通过更深入地了解 hasPermission 表达式的幕后情况来补充该答案。

回顾

让我们先回顾一下 this answer。回答者检测到 OP 确实打算使用带有两个参数的注释:

@PreAuthorize("hasPermission(#opetussuunnitelmaDto, 'LUONTI')")

混淆是因为 OP 在代码中看到了一个方法 hasPermission,它接受​​了三个参数,并且不知道要为第一个参数传递什么。答主确认是Spring框架本身提供了第一个参数,即Authentication对象,所以在注解中我们只需要传递两个参数即可。

深入探索

为了更详细地了解发生了什么,让我们分析 hasPermission 在 Spring OOTB 中的工作原理。我不会详细介绍每一个细节,但会勾勒出正在发生的事情的主要流程。希望这不仅会阐明哪个重载方法链接到 hasPermission SpEL 表达式,正如 OP 所要求的那样,而且还将揭示整个 ACL 框架如何解释 hasPermission 表达式下的兜帽;这将使我们对 hasPermission 表达式的含义以及如何解释和使用它更有信心。

所以让我们从头开始。

关于预授权/后授权的小说明

要理解 hasPermission 表达式,我们确实需要了解授权前/授权后。但是,由于 OP 没有询问这一点,因此假定它是已知的,并且我不会通过 @PreAuthorize@PostAuthorize 注释详细介绍方法保护。有关更多信息,请参阅 here 读者。在这里只想说,我们假设 hasPermission 表达式嵌入在这样的注释中,以保护方法或返回对象。 hasPermission 表达式反过来将评估为真或假。如果它评估为真,则 Spring 框架将允许在预授权的情况下继续方法调用,或者在后授权的情况下允许返回对象。否则,它将阻止访问。关于这些注释就足够了。我们真正想知道的是 Spring 如何解释 hasPermission 表达式本身,以得出真/假值。

权限评估器类

因此,hasPermission 将评估为真或假。但是如何?好吧,正如 OP 所暗示的那样,Spring 将权限评估委托给嵌套在 PermissionEvaluator Bean 内的 MethodSecurityExpressionHandler 对象。如果您已经设置了 Spring ACL,那么您很可能已经将 AclPermissionEvaluator 注册为 Spring 使用的权限评估器。例如,如果您使用代码配置了 Spring ACL,您可能会遇到这样的情况:

@Bean
public MethodSecurityExpressionHandler 
  defaultMethodSecurityExpressionHandler() {
    DefaultMethodSecurityExpressionHandler expressionHandler
      = new DefaultMethodSecurityExpressionHandler();
    AclPermissionEvaluator permissionEvaluator 
      = new AclPermissionEvaluator(aclService());
    expressionHandler.setPermissionEvaluator(permissionEvaluator);
    return expressionHandler;
}

如果你没有这样做,默认的权限评估器应该是 DenyAllPermissionEvaluator,我相信你已经猜到它会在所有情况下都拒绝权限:当然是安全的默认值。< /p>

从注解到方法

因此,将 AclPermissionEvaluator 类插入上述 Spring 安全框架后,Spring 表达式语言 (SpEL) 中的所有 hasPermission 表达式都将委托给 AclPermissionEvaluator 进行评估。我还没有研究 SpEL 表达式最终如何最终导致调用 AclPermissionEvaluator 中的方法的确切细节,但我认为不需要这些知识来解释 hasPermission 表达式的含义。 IMO,在这个级别上,所有需要知道的是哪个注释导致哪个方法调用。 this answer 已经涵盖了这一点。但让我在这里回顾一下。首先,我们注意到 hasPermission 方法在 AclPermissionEvaluator 中被重载,实际上在 PermissionEvaluator 的任何实现中。其中一个方法需要 3 个参数,另一个方法需要 4 个参数:

//3-Arg-Method
boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);

//4-Arg-Method
boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission);

另一方面,hasPermission 表达式也有两个用例。其中一个传入 2 个参数,另一个传入 3 个参数。这些已经在 this answer 中指出。但是让我们在这里将它们标记为表达式,而不是方法,以免混淆两者:

hasPermission('#targetDomainObject', 'permission')    //2-arg-expression
hasPermission('targetId', 'targetType', 'permission') //3-arg-expression

我们现在可以将两者联系起来:

  1. 如果使用了 //2-arg-expression,则调用 //3-Arg-Method
  2. 如果使用了 //3-arg-expression,则调用 //4-Arg-Method

这些方法从哪里获得额外的参数?同样,这已经得到了回答 here,但回顾一下,Spring 安全框架基于安全上下文提供的额外参数是这两种情况下的第一个参数,即 Authentication 参数的名称authentication。我还没有研究 Spring 框架究竟是如何做到这一点的,但对我来说,知道 Spring security 可以在这种上下文中获取身份验证对象就足够了。

好的,但是其他参数呢?让我们接下来看看这个。为了避免这个答案变得太大,我将只关注使用 //2-arg-expression 并调用 //3-Arg-Method 的情况。

hasPermission 方法的参数

如前所述,让我们只关注这个方法:

boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);

如前所述,第一个参数 authentication 对象是通过 Spring 安全性推断的。我还没有研究具体是如何发生的,但我相信为了这篇文章的目的,我们需要知道的就是了解身份验证对象包含:

  1. 用户,即委托人,例如“爱丽丝”
  2. 所有角色,即已授予该用户的权限,例如“管理员”或“编辑”

在 Spring ACL 中,我们使用通用术语 SID 来指代主体(例如“Alice”)或权限(例如“编辑器”)。因此,authentication 对象不仅包含一个 SID,还包含它们的整个列表。这个列表的顺序很重要,我们稍后会看到。

hasPermission 方法的其余参数通过 hasPermission 表达式传递。它们都输入为 Object。同样,为了让这篇文章更短一些,我将只关注一个用例。事实上,让我们关注 OP 提到的原始用例的稍微修改版本:

@PreAuthorize("hasPermission(#opetussuunnitelmaDto, 'READ')")
OpetussuunnitelmaDto addOpetussuunnitelma(OpetussuunnitelmaDto opetussuunnitelmaDto);
  1. 在子表达式 #opetussuunnitelmaDto 中使用 # 符号是在 SpEL 中指定方法 opetussuunnitelmaDtoaddOpetussuunnitelma 参数作为 {{1} 传入的一种方式targetDomainObject 方法的 }}。
  2. hasPermission 参数更简单:它只是作为 'READ' 直接传递给 String 方法的 permission 参数。

从参数中提取有用的信息

所以,我们现在知道所有参数是如何提供给这个方法的:

hasPermission

但是 boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission); 类型的参数从来没有多大用处。 Spring ACL 需要将这些参数转换为信息,以便从数据库中访问相关的 ACL 信息并进行权限检查。它通过委托 Object 方法来实现,该方法提取信息如下:

  1. 从身份验证对象获取 SID 的有序列表。例如,假设用户“Alice”已登录并且她具有“admin”和“editor”权限。然后这个列表将包含“Alice”、“admin”和“editor”的 SID。存储该列表的变量是 checkPermission。现在,此列表的顺序很重要。让我们考虑一下原因。假设您混合使用了授予和拒绝 ACE。例如,我们可能会授予所有编辑器访问某个对象的权限。但随后我们可能会拒绝用户 Jane。如果作为编辑的 Jane 尝试访问该对象,我们是根据她是 Jane 拒绝访问,还是根据她是编辑授予访问权限?因此,SID 列表的顺序很重要。第一个匹配的获胜。那么是什么控制了 SID 返回的顺序呢?嗯,这个责任在于 List<Sid> sids,默认情况下它是 SidRetrievalStrategy。通过查看此类的 SidRetrievalStrategyImpl 方法,我们看到主体 SID,即 Alice,在列表中处于主要位置。此后遵循授予的权限。我还没有深入研究权限本身是如何排序的细节,但在我看来这只是插入顺序,除了角色层次结构正在发挥作用的情况,在这种情况下,顺序可能遵循层次结构。对我来说,Alice 将被授予列表中的第一个位置是有道理的。如果爱丽丝自己被授予/拒绝访问任何东西,那么直觉上认为这会覆盖她根据她所拥有的角色被授予的任何东西。例如,如果我们想要拒绝对 Alice 的访问,即使她是一名编辑,那么该特定拒绝应该优先。另一方面,我们可能希望禁止所有编辑者访问一个对象,但对 Alice 进行例外处理。同样,将 Alice 放在列表中的首位可确保强制执行此逻辑。
  2. 权限对象,到目前为止只是一个 getSids,通过方法 Object 解析为一个 Permission 对象列表。存储它的变量是 resolvePermission。现在,回想一下,我们关注的是此权限是单个字符串的情况,即 List<Permission> requiredPermission。在这种情况下,如果 Spring 保留其默认行为,权限解析器将使用反射来检查此 "READ" 是否与类 String 中的所有静态常量进行比较,并返回匹配的常量。实际进行最终转换的代码是类BasePermission 中的方法buildFromMask。如果未找到名称与 DefaultPermissionFactory 匹配的 BasePermission 成员,则代码将抛出异常。实际上,在 OP 用例的情况下,给出的权限是 "READ",它不会匹配 "LUONTI" 中的任何内容 - 在这种情况下,开发人员需要覆盖 BasePermission 或创建他们自己的权限类。但我们不会在这里介绍。我们还注意到,一般情况下,表达式可能会产生一个权限列表,但在我们的特定情况下,我们只为传递到 SpEL 表达式的一个字符串获得一个权限。
  3. ACL 本身是基于对象检索的。实际上,在 BasePermission 方法中,域对象被转换为对象 ID,然后 hasPermission 使用该 ID 通过 ACL 服务查询该 ACL 的数据库:checkPermission

Spring 现在拥有执行 YES/NO 检查所需的所有信息:当前登录的用户是否有权访问此对象?它通过委托给 Acl acl = this.aclService.readAclById(oid, sids); Bean 上的 isGranted 方法来实现。默认情况下,这是通过 PermissionGrantingStrategy 实现的。

DefaultPermissionGrantingStrategy ...我们快到了

当我们查看此方法时,很明显,顺序对于 ACL 中的 ACE 列表和 SID 列表确实很重要。顺序对于权限列表也有些重要,但不那么重要 - 它决定的是哪个权限被解释为拒绝访问的“第一个”权限,如果 isGranted 表达式的结果为假;据我所知,这仅用于日志记录/调试目的,以便管理员可以尝试修复最有可能首先被破坏的权限。

对于 ACE 和 SID,顺序确实很重要,因为第一个与 SID 匹配的 ACE 优先,并且不会为该权限执行其他匹配。如果匹配结果为允许,则整个 isGranted 函数返回 true。否则,如果该权限没有匹配项,或者存在拒绝,则代码会转到下一个权限并尝试该权限。通过这种方式,我们可以看到权限列表是用 OR 类型的逻辑检查的:只需授予其中一个权限,isGranted 即可成功。

检查给定 ACE 是否与给定权限和 SID 匹配的实际逻辑如何?好吧,SID 位很简单:只需从 ACE 中取出 SID 字段并进行比较:isGranted。如果 SID 匹配,则调用重载的 ace.getSid().equals(sid) 函数,该函数仅比较掩码:

isGranted

IMO,这个方法真的应该被称为 protected boolean isGranted(AccessControlEntry ace, Permission p) { return ace.getPermission().getMask() == p.getMask(); } 之类的东西,因为它应该为允许(即授予)和拒绝类型的权限返回 true。它只是一个匹配函数 - 允许/拒绝行为存储在 isMatching 字段中。还有 some confusion 为什么不使用按位逻辑,但不用担心,如果您愿意,可以轻松覆盖该方法,如链接问题的答案中所述。

结论

回顾一下,OP 最初是这样问的:

<块引用>

spring security中如何解释hasPermission?

这个答案深入探讨了 ace.isGranting() 的机制,以了解如何解释它。总结:

  1. hasPermission SpEL 表达式链接到 Spring ACL 中 hasPermission 中重载的 hasPermission 方法之一,AclPermissionEvaluator 对象由 Spring 安全性自动填充。< /li>
  2. Authentication SpEL 表达式的参数通过 Spring ACL 机制向下传递。
  3. Spring ACL 检查三个列表:SID、权限、ACE(ACL 本身),对于其中两个列表,顺序很重要,以确定“用户是否有权访问此对象? "
  4. 每个权限只执行一次 ACE 匹配,并且匹配基于 SID 和重载的 hasPermission 函数,该函数可以被覆盖,例如如果开发人员想要使用按位逻辑。