为什么用户使用Spring Security和Grails更新而不调用save()?

时间:2016-01-11 21:32:56

标签: grails spring-security

概述:

  • User,UserRole和Role几乎都是标准型号 grails中的SpringSecurity插件
  • 我确保(在控制器中)的一件事是每个用户只有一个角色
  • 我的引导程序中创建了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()调用?

谢谢!

1 个答案:

答案 0 :(得分:5)

这与Spring Security无关 - 在使用插件使用的域类时,它只是巧合而已。

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

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

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

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

无关 - 这一行

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

应该只是

User currentUser = springSecurityService.currentUser

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