如何解释 spring 安全中的 hasPermission?

How to interpret hasPermission in spring security?

我是 spring 安全方面的新手。我该如何解释?

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

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

+public class PermissionEvaluator implements org.springframework.security.access.PermissionEvaluator {
+
+    @Override
+    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
+        LOG.error(" *** ei toteutettu *** ");
+        return true;
+    }
+
+    @Override
+    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
+        LOG.error(" *** ei toteutettu *** ");
+        return true;
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(PermissionEvaluator.class);
+}

Which method from the permission evaluator would get called?

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

会接到电话。

I read that the first argument ( the Authentication object) is not supplied.

它没有在您的注释中明确提供,而是由 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,但是传入一个 null id 没有意义,您要针对哪个实体进行权限检查?根据您应用@PreAuthorize 的方法,

OpetussuunnitelmaDto addOpetussuunnitelma(OpetussuunnitelmaDto opetussuunnitelmaDto);

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

已经触及了 OP 真正要问的问题的核心。我将通过 hasPermission 表达式更深入地了解幕后发生的事情来补充这个答案。

回顾

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

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

之所以会产生混淆,是因为 OP 在代码中看到一个方法 hasPermission,它接受三个参数,但无法弄清楚第一个参数要传递什么。回答者确认Spring框架本身提供了第一个参数,即Authentication对象,所以在注释中我们只需要传递两个参数。

深入探讨

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

所以让我们从头开始。

关于Pre/Post授权的小说明

要理解 hasPermission 表达式,我们确实需要理解 pre/post 授权。但是,由于 OP 没有询问这一点,因此假定它是已知的,并且我不会通过 @PreAuthorize@PostAuthorize 注释详细介绍方法保护。 reader 被引用 here 以获得更多信息。在这里我只想说,我们将假设 hasPermission 表达式嵌入到这样的注释中以保护方法或 return 对象。 hasPermission 表达式依次求值为真或假。如果计算结果为真,Spring 框架将允许方法调用在 pre-authorization 的情况下继续进行,或者将允许对象在 [=249= 的情况下被 returned ] 授权。否则,它将阻止访问。这些注释就足够了。我们真正想知道的是 Spring 如何解释 hasPermission 表达式本身,以得出 true/false 值。

权限评估者Class

因此,hasPermission 将评估为 true 或 false。但是怎么办?好吧,正如 OP 所暗示的那样,Spring 将权限评估委托给嵌套在 MethodSecurityExpressionHandler Bean 中的 PermissionEvaluator 对象。如果您设置了 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,我相信你已经猜到了在所有情况下都会拒绝权限:肯定是安全的默认值.

从注解到方法

因此,将 AclPermissionEvaluator class 插入 Spring 安全框架后,Spring 表达式语言(SpEL)中的所有 hasPermission 表达式) 将委托给 AclPermissionEvaluator 进行评估。我没有研究 SpEL 表达式最终如何最终导致调用 AclPermissionEvaluator 中的方法的确切细节,但我认为不需要这些知识来解释 hasPermission 表达式的含义。 IMO,在这个级别,所有需要知道的是哪个注释导致哪个方法调用。 已经涵盖了这一点。但让我在这里重述一下。首先,我们注意到 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 个参数。 中已经指出了这些。但是我们在这里将它们标记为表达式,而不是方法,以免混淆两者:

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

我们现在可以link两个:

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

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

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

参数到hasPermission 方法

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

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

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

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

在 Spring ACL 中,我们使用通用术语 SID 来指代诸如“Alice”之类的委托人,或诸如“编辑”之类的授权机构。因此,authentication 对象不仅包含一个 SID,还包含它们的完整列表。这个列表的顺序很重要,我们稍后会看到。

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

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

从参数中提取有用的信息

所以,我们现在知道如何将所有参数提供给此方法:

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

但是Object类型的参数从来没有多大用处。 Spring ACL 需要将这些参数转换成信息才能用来从数据库中访问相关的ACL 信息并进行权限检查。它通过委托给 checkPermission 方法来实现,该方法提取信息如下:

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

Spring 现在拥有执行 YES/NO 检查所需的所有信息:当前登录的用户是否有权访问该对象?它通过委托给 PermissionGrantingStrategy Bean 上的 isGranted 方法来实现。默认情况下,这是通过 DefaultPermissionGrantingStrategy.

实现的

isGranted ...我们快到了

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

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

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

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

IMO,这个方法真的应该被称为 isMatching 因为它应该 return 对于允许(即授予)和拒绝类型的权限都是正确的。它只是一个匹配函数 - allow/deny 行为存储在 ace.isGranting() 字段中。此外,函数名isGranted被重载*,更加混乱。

还有 some confusion 为什么这不使用按位逻辑,但别担心,如果您愿意,您可以轻松地覆盖该方法,如 [=248= 的答案中指定的那样]ed 问题。

结论

回顾一下,OP 最初问:

How to interpret hasPermission in spring security?

这个答案深入探讨了 hasPermission 的机制,以帮助理解如何解释它。总结:

  1. hasPermission SpEL 表达式 link 到 Spring ACL 中 AclPermissionEvaluator 中重载的 hasPermission 方法之一,Authentication 由 Spring security.
  2. 自动填充的对象
  3. hasPermission SpEL 表达式的参数通过 Spring ACL 机制向下传递。
  4. Spring ACL 检查三个列表:SID、权限、ACE(ACL 本身),对于其中两个列表,顺序很重要以确定最终 YES/NO 回答问题“是否用户有权访问此对象?
  5. 每个权限只执行一次 ACE 匹配,匹配基于 SID 和重载的 isGranted 函数,可以重载,例如如果开发者想使用按位逻辑。

脚注

*isGranted函数有两个版本。 public 确实会检查列表中的某些权限是否已授予某些 SID。而受保护的确实应该被称为 isMatching 之类的名称,因为它会检查匹配的 ACE。