为啥用户在不调用 save() 的情况下使用 Spring Security 和 Grails 进行更新?

Posted

技术标签:

【中文标题】为啥用户在不调用 save() 的情况下使用 Spring Security 和 Grails 进行更新?【英文标题】:Why does a user get updated using Spring Security and Grails without calling save()?为什么用户在不调用 save() 的情况下使用 Spring Security 和 Grails 进行更新? 【发布时间】:2016-04-16 08:24:03 【问题描述】:

概述:

User、UserRole 和 Role 几乎是标准模型 grails 中的 SpringSecurity 插件 我确保(在控制器中)的一件事是每个用户只有 1 个角色 在我的引导程序中创建了 3 个角色:管理员、经理和用户 管理员可以执行所有操作 经理可以更新其他经理和用户 用户只能更新自己(但不能更新他们的角色)

在我的控制器中,我通常使用注释来控制大部分安全性,但对于更新和保存操作,我添加了逻辑以进行更高级的检查:

@Transactional
def update(User userInstance) 
    User currentUser = User.get(springSecurityService.currentUser?.id)
    Role currentRole = currentUser.getAuthorities().getAt(0)    // There is only 1 role per user, so give the first

    Role roleInstance = Role.get(params['role.id'])

    // SECURITY LOGIC -> Move to service
    if (currentRole.authority.equals("ROLE_USER")) 

        if (userInstance != currentUser || !roleInstance.authority.equals("ROLE_USER")) 
            notAllowed(userInstance)
            return
        
     else if (currentRole.authority.equals('ROLE_MANAGER')) 
        ...
    
    ...
    // REST OF CODE - User is saved here

现在我遇到了一个奇怪的问题。如果我以 ROLE_USER 身份登录并更新 ROLE_ADMIN,我会收到我应该得到的 notAllowed 错误消息,并且操作会立即返回,因此它不会继续到实际保存用户的 REST OF CODE。

如果我查看管理员,它实际上已经更新(持续)。为什么会出现这种情况,因为它从未调用过 save() 调用?

谢谢!

【问题讨论】:

你的其余代码(//REST OF CODE - 用户保存在这里)正在执行,将其放入 else 块然后尝试。我很确定即使在 notAllowed 被调用并返回之后,保存代码仍在运行。 @VinayPrajapati 不,它没有达到 REST OF CODE,我已经运行了调试器并进行了验证。请参阅下面 Burt 的回答,了解为什么无论是否调用 save 都会保存。 是的!在这种情况下,这是正确的答案。 【参考方案1】:

这与 Spring Security 无关——这只是一个巧合,它是在使用插件使用的域类时发生的。

默认情况下,Grails 使用“在视图中打开会话”模式,这在使用 Hibernate 时很常见。在每个请求开始时,会创建一个 Hibernate Session 并将其存储在 ThreadLocal 中,如果可用,持久性代码会使用它,并在请求结束时刷新并关闭会话。

这在处理延迟加载的实例和集合时特别有用。如果没有现有的 Hibernate Session 可用,持久性代码会创建一个并使用它从数据库中检索实例,但由于它创建了会话,它会在查询完成后关闭它。这会使实例与任何会话断开连接,并且没有自动重新附加逻辑,因此如果您在对象断开连接后尝试访问未初始化的延迟加载实例或集合,则会出现异常。但是如果已经有一个打开的会话,持久性代码会使用它但不会关闭它,所以加载的实例会被附加并且延迟加载将起作用。

您看到的是 Hibernate 检测到持久实例已被修改,默认情况下,当会话关闭时,它会检测到更改并帮助您将它们刷新到数据库中。无论是否调用save(),都会发生这种情况,因此实际上您通常需要调用save() 的唯一时间是在插入新实例时。

您可以在视图支持中禁用打开会话,但这样做会损失很多,而且这通常不是一个好主意。您还可以自定义它的工作方式、刷新时间等。但一般来说,您应该断开不希望自动刷新的附加实例。有一个 GORM 方法——discard()——如果你在一个修改过的实例上调用它,当刷新发生时 Hibernate 不会意识到它并且不会保存任何东西。

不相关 - 此行

User currentUser = User.get(springSecurityService.currentUser?.id)

应该是

User currentUser = springSecurityService.currentUser

因为getCurrentUser() 方法使用来自安全认证的缓存ID 从数据库中检索用户实例。您正在使用该 User 实例来获取其 id,然后将其丢弃,并使用该 id 再次加载相同的 User。

【讨论】:

谢谢伯特!学习 Grails 就是学习许多技术!还要感谢您对 currentUser 的关注! @Burt Beckwith。我们如何禁用开放会话? 附带说明,discard() 必须发生在 notAllowed() 调用之前,而不是之后。 notAllowed() 方法与标准的 notFound() 方法非常相似。再次感谢 Burt 的解释!

以上是关于为啥用户在不调用 save() 的情况下使用 Spring Security 和 Grails 进行更新?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我可以在不使用 std::getline 的情况下调用 getline?

为啥切片函数在不明确使用 dplyr 的情况下不起作用

为啥 glDrawElments() 在不使用任何着色器的情况下工作?

为啥c ++ std::hash会创建一个仿函数结构并且可以在不每次创建结构的情况下调用它

Django:如何在不使用 SESSION_SAVE_EVERY_REQUEST 的情况下为 AnonymousUser 设置 sessionid cookie

Firebase 消息:我可以在不调用浏览器提示的情况下检查用户是不是已经拥有令牌?