为什么用户在不调用save()的情况下使用Spring Security和Grails进行更新? [英] Why does a user get updated using Spring Security and Grails without calling save()?

查看:105
本文介绍了为什么用户在不调用save()的情况下使用Spring Security和Grails进行更新?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

概述:


  • 用户,UserRole和Role几乎是
    中的标准模型grails中的SpringSecurity插件

  • 我确保的一件事(在控制器中)是每个用户只有1个角色<​​/ li>
  • 在我的引导中创建了3个角色:Admin,Manager和用户


    • 管理员可以执行所有操作

    • 管理员可以更新其他经理和用户
    • 用户只能自行更新(而不是他们的角色)


      在我的控制器中,我使用注释来控制大部分安全性,但为了更新和保存操作,我添加了更多预先检查的逻辑:

        @Transactional 
      def update(User userInstance){
      User userUser = User.get(springSecurityService.currentUser?.id)
      角色currentRole = currentUser.getAuthorities()。getAt(0 )//每个用户只有1个角色,所以给出第一个

      角色角色Instance = Role.get(params ['role.id'])

      // SECURITY LOGIC - >移动到服务
      if(currentRole.authority.equals(ROLE_USER)){

      if(userInstance!= currentUser ||!roleInstance.authority.equals(ROLE_USER)){
      notAllowed(userInstance)
      return
      }
      } else if(currentRole.authority.equals('ROLE_MANAGER')){
      ...
      }
      ...
      //代码的剩余部分 - 用户保存在这里
      }

      现在,我在这里得到一个奇怪的问题。如果我以ROLE_USER身份登录并更新ROLE_ADMIN,则会收到notAllowed错误消息,并且该操作会在其后立即返回,但不会继续执行用户实际保存的REST代码。



      如果我查看管理员,它实际上已更新(持续)。为什么是这种情况,因为它从来没有进入save()调用?



      谢谢!

      解决方案

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



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



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



      重新看到Hibernate是否检测到持久实例已被修改,并且默认情况下,当会话关闭时,它将检测到这些更改并帮助将它们刷新到数据库。这发生在有或没有 save()调用的情况下,所以实际上通常只需要调用 save()是在插入新实例时。



      您可以在视图支持中禁用打开的会话,但通过这样做会损失很多,这通常不是一个好主意。您还可以自定义工作方式,发生刷新时等。但通常情况下,您应断开不想自动刷新的附加实例。有一个GORM方法 - discard() - 如果您在修改的实例上调用它,当flush发生时Hibernate不会知道它,并且不会保存任何内容。

      无关 - 此行

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

      应该是

       用户currentUser = springSecurityService.currentUser 

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


      Overview:

      • User, UserRole and Role are pretty much the standard model from SpringSecurity plugin in grails
      • One thing that I ensure (in the controller) is that there is only 1 role per user
      • There are 3 roles created in my bootstrap: Admin, Manager and User
        • Admins can do all actions
        • Manager can update other managers and users
        • Users can only update themselves (but not their roles)

      In my controller I use annotations to generally control most of the security but for the update and save actions I add the logic for more advance checking:

      @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
      }
      

      Now here is where I get a weird problem. If I log in as a ROLE_USER and update an ROLE_ADMIN I get the notAllowed error message as I should and the action returns right after so it doesn't continue to the REST OF CODE where the User is actually saved.

      If I look at the admin it actually has been updated (persisted). Why is this the case since it never got to the save() call?

      Thanks!

      解决方案

      This has nothing to do with Spring Security - it's just a coincidence that it's happening when working with domain classes that are used by the plugin.

      By default Grails uses the "open session in view" pattern, which is common when using Hibernate. At the beginning of each request a Hibernate Session is created and stored in a ThreadLocal and the persistence code uses that if it's available, and at the end of the request the session is flushed and closed.

      This is particularly helpful when working with lazy-loaded instances and collections. If there isn't an existing Hibernate Session available, the persistence code creates one and uses it to retrieve instances from the database, but since it created the session, it closes it after the query finishes. This leaves the instances disconnected from any session and there's no auto-reattach logic, so if you attempt to access a non-initialized lazy-loaded instance or collection after the object is disconnected you get an exception. But if there was an open session already, the persistence code uses it but doesn't close it, so loaded instances are attached and lazy loading will work.

      What you're seeing is Hibernate detecting that a persistent instance has been modified, and by default when the session is closed it will detect the changes and helpfully flush them to the database for you. This happens with or without a save() call, so in effect the only time you typically need to call save() is when inserting new instances.

      You can disable the open session in view support, but you lose a lot by doing this and it's in general not a good idea. You can also customize how it works, when flushing happens, etc. But in general you should disconnect attached instances that you don't want to be auto-flushed. There's a GORM method for that - discard() - and if you call it on a modified instance Hibernate won't be aware of it when the flush happens and nothing will be saved.

      Unrelated - this line

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

      should just be

      User currentUser = springSecurityService.currentUser
      

      since the getCurrentUser() method retrieves the User instance from the database using the cached id from the security authentication. You're using that User instance to get its id and then throwing it away, and using the id to load the same User again.

      这篇关于为什么用户在不调用save()的情况下使用Spring Security和Grails进行更新?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆