我是春天安全的新手。我怎么解释这个?
Timestamp sqlDate = new Timestamp( System.currentTimeMillis() );
preparedStmt.setTimestamp( ++startIndex, sqlDate );
来自权限评估程序的哪个方法会被调用?我想在这种情况下会调用带有三个参数的那个。它正在检查当前用户是否对类型目标''opetussuunnitelma'具有'LUONTI'权限。我对吗?我们不能只包括“null”并且只传递两个参数。我读到第一个参数(Authentication对象)没有提供。
@PreAuthorize("hasPermission(null, 'opetussuunnitelma', 'LUONTI')")
OpetussuunnitelmaDto addOpetussuunnitelma(OpetussuunnitelmaDto opetussuunnitelmaDto);
答案 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
我们现在可以将两者联系起来:
//2-arg-expression
,则调用 //3-Arg-Method
。//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 安全性推断的。我还没有研究具体是如何发生的,但我相信为了这篇文章的目的,我们需要知道的就是了解身份验证对象包含:
在 Spring ACL 中,我们使用通用术语 SID 来指代主体(例如“Alice”)或权限(例如“编辑器”)。因此,authentication
对象不仅包含一个 SID,还包含它们的整个列表。这个列表的顺序很重要,我们稍后会看到。
hasPermission
方法的其余参数通过 hasPermission
表达式传递。它们都输入为 Object
。同样,为了让这篇文章更短一些,我将只关注一个用例。事实上,让我们关注 OP 提到的原始用例的稍微修改版本:
@PreAuthorize("hasPermission(#opetussuunnitelmaDto, 'READ')")
OpetussuunnitelmaDto addOpetussuunnitelma(OpetussuunnitelmaDto opetussuunnitelmaDto);
#opetussuunnitelmaDto
中使用 # 符号是在 SpEL 中指定方法 opetussuunnitelmaDto
的 addOpetussuunnitelma
参数作为 {{1} 传入的一种方式targetDomainObject
方法的 }}。hasPermission
参数更简单:它只是作为 'READ'
直接传递给 String
方法的 permission
参数。所以,我们现在知道所有参数是如何提供给这个方法的:
hasPermission
但是 boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);
类型的参数从来没有多大用处。 Spring ACL 需要将这些参数转换为信息,以便从数据库中访问相关的 ACL 信息并进行权限检查。它通过委托 Object
方法来实现,该方法提取信息如下:
checkPermission
。现在,此列表的顺序很重要。让我们考虑一下原因。假设您混合使用了授予和拒绝 ACE。例如,我们可能会授予所有编辑器访问某个对象的权限。但随后我们可能会拒绝用户 Jane。如果作为编辑的 Jane 尝试访问该对象,我们是根据她是 Jane 拒绝访问,还是根据她是编辑授予访问权限?因此,SID 列表的顺序很重要。第一个匹配的获胜。那么是什么控制了 SID 返回的顺序呢?嗯,这个责任在于 List<Sid> sids
,默认情况下它是 SidRetrievalStrategy
。通过查看此类的 SidRetrievalStrategyImpl
方法,我们看到主体 SID,即 Alice,在列表中处于主要位置。此后遵循授予的权限。我还没有深入研究权限本身是如何排序的细节,但在我看来这只是插入顺序,除了角色层次结构正在发挥作用的情况,在这种情况下,顺序可能遵循层次结构。对我来说,Alice 将被授予列表中的第一个位置是有道理的。如果爱丽丝自己被授予/拒绝访问任何东西,那么直觉上认为这会覆盖她根据她所拥有的角色被授予的任何东西。例如,如果我们想要拒绝对 Alice 的访问,即使她是一名编辑,那么该特定拒绝应该优先。另一方面,我们可能希望禁止所有编辑者访问一个对象,但对 Alice 进行例外处理。同样,将 Alice 放在列表中的首位可确保强制执行此逻辑。getSids
,通过方法 Object
解析为一个 Permission
对象列表。存储它的变量是 resolvePermission
。现在,回想一下,我们关注的是此权限是单个字符串的情况,即 List<Permission> requiredPermission
。在这种情况下,如果 Spring 保留其默认行为,权限解析器将使用反射来检查此 "READ"
是否与类 String
中的所有静态常量进行比较,并返回匹配的常量。实际进行最终转换的代码是类BasePermission
中的方法buildFromMask
。如果未找到名称与 DefaultPermissionFactory
匹配的 BasePermission
成员,则代码将抛出异常。实际上,在 OP 用例的情况下,给出的权限是 "READ"
,它不会匹配 "LUONTI"
中的任何内容 - 在这种情况下,开发人员需要覆盖 BasePermission
或创建他们自己的权限类。但我们不会在这里介绍。我们还注意到,一般情况下,表达式可能会产生一个权限列表,但在我们的特定情况下,我们只为传递到 SpEL 表达式的一个字符串获得一个权限。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()
的机制,以了解如何解释它。总结:
hasPermission
SpEL 表达式链接到 Spring ACL 中 hasPermission
中重载的 hasPermission
方法之一,AclPermissionEvaluator
对象由 Spring 安全性自动填充。< /li>
Authentication
SpEL 表达式的参数通过 Spring ACL 机制向下传递。hasPermission
函数,该函数可以被覆盖,例如如果开发人员想要使用按位逻辑。