使用用户组和角色时 Grails / Spring Security 中的错误 - 无法进行身份验证

Posted

技术标签:

【中文标题】使用用户组和角色时 Grails / Spring Security 中的错误 - 无法进行身份验证【英文标题】:Bug in Grails / Spring Security when using user groups and roles - can't authenticate 【发布时间】:2017-07-16 13:27:58 【问题描述】:

我想我在 Grails Spring Security 3.1.1 和最新的 Grails 3.2.6 中发现了一个错误。

我已经安装了 Spring Security 插件。

我从命令行控制台执行了以下操作:

grails s2-quickstart org.softwood.security User Role --groupClassName=UserGroup

创建一个用户、角色和用户组表,因为我想使用将角色分配给组功能。然后我配置了域类,并在引导程序中添加了一些用户来测试它,如下所示:

def loadSecurityUserAndRoles () 
    //plugin requires ROLE_ prefix see section 4.2/p18

    Role adminRole = new Role(authority: 'ROLE_ADMIN').save(failOnError:true)
    Role userRole = new Role(authority: 'ROLE_USER').save(failOnError:true)
    Role xtraRole = new Role(authority: 'ROLE_XTRA').save(failOnError:true)
    UserGroup adminGroup = new UserGroup (name:"GROUP_ADMIN").save(failOnError:true)
    UserGroup userGroup = new UserGroup (name:"GROUP_USERS").save(failOnError:true)

    User userWill = new User(username: 'will', password: 'password').save(failOnError:true)
    User userMaz = new User(username: 'maz', password: 'password').save(failOnError:true)
    User userMeg = new User(username: 'meg', password: 'password').save(failOnError:true)

    //give adminGroup admin and user roles
    UserGroupToRole sgr = UserGroupToRole.create(adminGroup, adminRole)
    sgr = UserGroupToRole.create(adminGroup, userRole)

    sgr = UserGroupToRole.create(userGroup, userRole)

    assert UserGroupToRole.count() == 3

    def auth2 = adminGroup.getAuthorities()
    println "adminGroup authorities returned $auth2 "

    //assign test user to adminGroup, and maz+meg to user group, inherit all group roles
    UserToUserGroup su2g = UserToUserGroup.create (userWill, adminGroup, true)
    su2g = UserToUserGroup.create (userMaz, userGroup, true)
    su2g = UserToUserGroup.create (userMeg, userGroup, true)

    //assign individual 'xtra' role to user
    UserToRole sxtra = UserToRole.create(userWill, xtraRole, true)
    assert UserToRole.count() == 1

    def auth = userWill.getAuthorities()
    assert auth.collectit.authority.sort() == ['ROLE_ADMIN', 'ROLE_USER', 'ROLE_XTRA']
    println "userWill authorities returned $auth "

    def mazAuth = userMaz.getAuthorities()
    def megAuth = userMeg.getAuthorities()
    println "user authorities returned maz: '$mazAuth', and meg: '$megAuth' "


    def groups = userWill.getUserGroups()
    assert groups.collectit.name.sort() == ['GROUP_ADMIN']

    assert UserGroup.count() == 2
    assert User.count() == 3
    assert Role.count() == 3
    assert UserToUserGroup.count() == 3
    assert UserGroupToRole.count() == 3
    assert UserToRole.count() == 1


这一切似乎都按预期工作,当我断言<userInst>.getAuthorities() 时,基本断言会为每个用户返回正确数量的角色:

然后我设置了一个控制器secureTest 以打开动作并保护一个

class SecureTestController 

    def index() 
        render "hello Will you passed the permit_any"
    

    @Secured ('ROLE_ADMIN')
    def secure () 
        render "hello Will you passed the ROLE_ADMIN"

    

我运行应用程序 - 它启动,我将浏览器指向 secureTest/index - 作为打开的 url 工作正常。

当我将浏览器指向secureTest/secure 时,它​​会抛出默认登录页面。我填写了遗嘱/密码,它会抛出堆栈跟踪并且无法登录。

我认为该跟踪的关键部分在这里:

Caused by: groovy.lang.MissingPropertyException: No such property: authorities for class: org.softwood.security.Role
Possible solutions: authority
    at org.grails.datastore.gorm.GormInstanceApi.propertyMissing(GormInstanceApi.groovy:55)
    at org.grails.datastore.gorm.GormEntity$Trait$Helper.propertyMissing(GormEntity.groovy:57)
    at org.grails.datastore.gorm.GormEntity$Trait$Helper$propertyMissing$9.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
    at org.softwood.security.Role.propertyMissing(Role.groovy)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaClassImpl.invokeMissingProperty(MetaClassImpl.java:880)
    at groovy.lang.MetaClassImpl.getProperty(MetaClassImpl.java:1861)
    at groovy.lang.MetaClassImpl.getProperty(MetaClassImpl.java:3735)
    at org.softwood.security.Role.getProperty(Role.groovy)
    at org.codehaus.groovy.runtime.InvokerHelper.getProperty(InvokerHelper.java:172)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getProperty(ScriptBytecodeAdapter.java:456)
    at grails.plugin.springsecurity.userdetails.GormUserDetailsService$_loadAuthorities_closure2.doCall(GormUserDetailsService.groovy:92)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1024)
    at groovy.lang.Closure.call(Closure.java:414)
    at groovy.lang.Closure.call(Closure.java:430)

我认为该方法在这里确实失败了(GormUserDetailsS​​ervice.groovy:92)。

当您单击该链接时,编辑器会将您带到插件中。

protected Collection<GrantedAuthority> loadAuthorities(user, String username, boolean loadRoles) 
    if (!loadRoles) 
        return []
    

    def conf = SpringSecurityUtils.securityConfig

    String authoritiesPropertyName = conf.userLookup.authoritiesPropertyName
    String authorityPropertyName = conf.authority.nameField

    boolean useGroups = conf.useRoleGroups
    String authorityGroupPropertyName = conf.authority.groupAuthorityNameField

    Collection<?> userAuthorities = user."$authoritiesPropertyName"
    def authorities

    if (useGroups) 
        if (authorityGroupPropertyName) 
            authorities = userAuthorities.collect  it."$authorityGroupPropertyName" .flatten().unique().collect  new SimpleGrantedAuthority(it."$authorityPropertyName") 
        
        else 
            log.warn 'Attempted to use group authorities, but the authority name field for the group class has not been defined.'
        
    
    else 
        authorities = userAuthorities.collect  new SimpleGrantedAuthority(it."$authorityPropertyName") 
    
    authorities ?: [NO_ROLE]

这里的关键部分是这个调用序列:

    if (useGroups) 
        if (authorityGroupPropertyName) 
            authorities = userAuthorities.collect  it."$authorityGroupPropertyName" .flatten().unique().collect  new SimpleGrantedAuthority(it."$authorityPropertyName") 
        

useGroups 是真的。我有一个通过快速安装脚本在 application.groovy 文件中设置的 authorityGroupPropertyName:

grails.plugin.springsecurity.authority.groupAuthorityNameField = 'authorities'

所以上面的代码行调用:

userAuthorities.collect  it."$authorityGroupPropertyName" .flatten().unique()

这会返回一个包含 role.authority 名称的 hashSet 作为字符串,而 flatten/unique 只是确保没有嵌套结构并且字符串是唯一的。到目前为止一切顺利。

最后一点是我认为的错误。

<hashSet of role Names>.collect  new SimpleGrantedAuthority(it."$authorityPropertyName") 

在此位中,对字符串集调用 collect 方法,但传递给“SimpleGrantedAuthority”的字符串应该只是字符串。而是它的召唤

it."$authorityPropertyName" 

它是一个字符串并且没有这样的属性。

application.groovy 中设置的关键位是:

grails.plugin.springsecurity.userLookup.userDomainClassName = 'org.softwood.security.User'
grails.plugin.springsecurity.userLookup.authoritiesPropertyName = 'authorities'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'org.softwood.security.UserToUserGroup'
grails.plugin.springsecurity.authority.className = 'org.softwood.security.Role'
grails.plugin.springsecurity.authority.groupAuthorityNameField = 'authorities' //'authority'
grails.plugin.springsecurity.useRoleGroups = true

如您所见,我尝试将权限更改为“权限”,因为这是角色类中的属性名称。这也因缺少属性消息而失败。

我认为这是一个错误,代码应该只是通过了'it':

.collect new SimpleGrantedAuthority(it)

生成&lt;SimpleGrantedAuthority&gt;类型的hashSet。

还有其他人在使用 Spring Security 时遇到过这个问题吗?我不敢相信我是第一个被它绊倒的人,或者也许没有人试图使用组?

【问题讨论】:

getAuthorities 方法是由 User.groovy 类中的插件生成的,例如我有一个稍微不同的设置,你可以调整它们以不同的方式工作,也许你需要微调这一点。 Set getAuthorities() UserRoleGroup.findAllByUser(this)*.roleGroup vahid - 我对类进行了调整 - 但测试了断言和响应 - 我允许通过 userGroup 继承的角色 - 以及个人角色分配 - 我在 github 上有代码所以感觉获取克隆github.com/woodmawa/coffeeShopApp。我理解 user.getAuthorities() 要做的是返回一组角色(权限),我已经浏览了代码,它就是这样做的。但我突出显示的代码会中断 我可能误解了会发生什么 - 但如果您查看仅用户/角色(无组)的代码部分,authorities = userAuthorities.collect new SimpleGrantedAuthority(it."$authorityPropertyName") 您会看到它在一组上工作在这种情况下,it."$authorityPropertyName" 工作正常。但它不能在组案例authorities = userAuthorities.collect it."$authorityGroupPropertyName" .flatten().unique().collect new SimpleGrantedAuthority(it."$authorityPropertyName") 中工作,因为这里有两个集合。我明天再检查一次 对不起,我没有时间深入研究您的问题,老实说,我有很多自己的问题,只是想尽可能地提供一些帮助。无论如何,您指出 github.com/grails-plugins/grails-spring-security-core/blob/… 的那一行是在当前版本的插件中,所以不确定它是否会导致您的问题 感谢 vahid - 我想我需要报告它,我可以说这不能像团队预期的那样工作。只是不确定我如何在不拉动项目并修改代码的情况下对其进行编码 - 我很紧张 【参考方案1】:

好的,就在我睡觉之前 - 我将 GormUserDetailsS​​ervice 中的代码复制到我的引导程序中,这样我就可以在我自己的文件空间中扩展/播放。

我修改了 if 块并像这样展开了

    if (useGroups) 
            if (authorityGroupPropertyName) 
                //userAuthorities returns Set<Role>
                println """ debug
authoritiesPropertyName = $authoritiesPropertyName
authorityPropertyName = $authorityPropertyName
authorityGroupPropertyName = $authorityGroupPropertyName
userAuthorities returns $userAuthorities of type $userAuthorities.getClass()

"""
                 def roles = userAuthorities.collect  it."$authorityGroupPropertyName" .flatten().unique()

                authorities = roles.collect  new SimpleGrantedAuthority(it."$authorityPropertyName") 

我的调试字符串显示在控制台上

 debug
authoritiesPropertyName = authorities
authorityPropertyName = authority
authorityGroupPropertyName = authority
userAuthorities returns [Role(authority:ROLE_XTRA), Role(authority:ROLE_USER), Role(authority:ROLE_ADMIN)] of type class java.util.HashSet

返回到变量“roles”的第一个结果是一个字符串的 ArrayList(每个 role.authority 名称实例。该用户具有正确的值,就像之前为 userWill 设置的引导程序一样。

下一个代码现在失败,因为我有一个字符串数组列表,它试图访问一个属性

 authorities = roles.collect  new SimpleGrantedAuthority(it."$authorityPropertyName") 

失败了

groovy.lang.MissingPropertyException: No such property: authority for class: java.lang.String
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:53)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getProperty(ScriptBytecodeAdapter.java:458)
    at coffeeshopapp.BootStrap$_loadSecurityUserAndRoles_closure6.doCall(BootStrap.groovy:115)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1024)
    at groovy.lang.Closure.call(Closure.java:414)
    at groovy.lang.Closure.call(Closure.java:430)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.collect(DefaultGroovyMethods.java:3170)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.collect(DefaultGroovyMethods.java:3140)
    at org.codehaus.groovy.runtime.dgm$66.invoke(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:274)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
    at coffeeshopapp.BootStrap.loadSecurityUserAndRoles(BootStrap.groovy:115)

无论我以哪种方式在原始源代码中剪掉这一行,都行不通。原行再次读取

        authorities = userAuthorities.collect  it."$authorityGroupPropertyName" .flatten().unique().collect  new SimpleGrantedAuthority(it."$authorityPropertyName") 

如果它改为这样读取 - 代码将起作用

        authorities = userAuthorities.collect  it."$authorityGroupPropertyName" .flatten().unique().collect  new SimpleGrantedAuthority(it) 

v3.1.1 插件中的代码肯定不能工作吗?

【讨论】:

【参考方案2】:

我也配置了用户角色和角色组,这就是我的工作方式:

在用户类中:

   Set<RoleGroup> getAuthorities() 
        UserRoleGroup.findAllByUser(this)*.roleGroup
    
   //not used
   // Set<RoleGroup> getAuthoritiesNames() 
   //     UserRoleGroup.findAllByUser(this)*.roleGroup?.name
   // 

    Set<Role> getAuthority() 
        UserRole.findAllByUser(this).collect  it.role  as Set
    

在 RoleGroup.groovy 我有:(认为我改变了这些不确定)

Set<Role> getAuthorities() 
        RoleGroupRole.findAllByRoleGroup(this)*.role
    
    def getAuthority() 
        RoleGroupRole.withTransaction 
            RoleGroupRole.findAllByRoleGroup(this)*.role.authority[0]
        
    

在我的引导程序中,这样的事情会创建绑定到角色和角色组的默认管理员帐户:

private void addAdminUser(String username) 
        User adminUser = User.findByUsername(username)
        if (!adminUser) 

            User.withTransaction 
                adminUser = new User(username: username, password: 'PASSWORD'
                )
                adminUser.save(flush: true)
            
        
        def adminRole
        Role.withTransaction 
            adminRole= Role.findByAuthority('ROLE_ADMIN')
            if (!adminRole) 
                adminRole = new Role(authority: 'ROLE_ADMIN').save(flush: true)
                UserRole.create adminUser, adminRole,true
            
        
        def adminRoleGroup
        RoleGroup.withTransaction 
            adminRoleGroup = RoleGroup.findByName('ADMINS')
            if (!adminRoleGroup) 
                adminRoleGroup = new RoleGroup(name: 'ADMINS').save(flush: true)
            
        
        UserRoleGroup.withTransaction 
            def adminRoleGroupRole = RoleGroupRole.findByRole(adminRole)
            if (!adminRoleGroupRole) 
                adminRoleGroupRole = new RoleGroupRole(role: adminRole, roleGroup: adminRoleGroup).save(flush: true)
                new UserRoleGroup(user: adminUser, roleGroup: adminRoleGroup).save(flush: true)
            
        
    

【讨论】:

【参考方案3】:

好的 vahid - 认为我已经剥洋葱并发现了问题。

第 1 步。回到开始并从头开始创建一个新项目,并在空项目上添加了 grails-security 插件。从 grails 控制台按照在线指南 grails s2-quickstart org.softwood User Role --groupClassName=RoleGroup

这很有效,所以我将它产生的内容与我自己陷入的内容进行了比较,并认为我明白我的问题出在哪里。

我的角色类与空测试一相同 - 没有区别。

然后我进入了 User 类,不知何故我得到了 getAuthorities() 返回 Set(这似乎是明智的,因为我的 UserGroup(又名 RoleGroup 在新开始)中的 getAuthorites() 返回 Set。基本的用户/角色返回集合

如果你生成一个只有用户和角色的项目,那么 User.getAuthorities() 返回集合。但是,当使用组时,模板会发生变化并返回 Set。

这种差异至关重要。因为调整其取消引用策略的代码位于 GormUserDetailsS​​ervice.loadAuthorities() 的代码中,无法看到它 - 请参阅我的“调整版本 ID 一直在尝试修复...

    if (useGroups) 
        if (authorityGroupPropertyName) 
            //authorities = userAuthorities.collect  it."$authorityGroupPropertyName" .flatten().unique().collect  new SimpleGrantedAuthority(it."$authorityPropertyName") 
            //ww edit to stop gpf..  userAuthorties is Set<Role>, so first collect gets the names and produces ArrayList of stirng
            //second collect builds the authorities as Set<SimpleGrantedAuthority>
            authorities = userAuthorities.collect  it."$authorityGroupPropertyName" .flatten().unique().collect  new SimpleGrantedAuthority(it) 
        
        else 
            log.warn 'Attempted to use group authorities, but the authority name field for the group class has not been defined.'
        
    
    else 
        authorities = userAuthorities.collect  new SimpleGrantedAuthority(it."$authorityPropertyName") 
    

问题在于 def authorities 变量会根据使用用户/角色或用户/组/角色模型而改变类型。

我陷入了困境,因为我试图使用组(默认分配模型)但允许个人角色分配(个人授予的角色,作为覆盖工具)。

当我阅读User.authorities 属性时 - 我假设这将返回它所链接的角色(通​​过组和我的个人分配覆盖)(如在单用户/角色模型中)。我在类用户中的域类代码中对此进行了整理

Set<Role> getAuthorities() 
    //orig UserUserGroupBroken.findAllByUser(this)*.userGroup

    Set<Role> individualRoles = UserToRole.findAllByUser(this)*.role
    Set<UserGroup> groups = UserToUserGroup.findAllByUser(this)*.group
    Set<Role> groupRoles = groups.collectit.getAuthorities() 
    Set<Role> aggregateRoles = new HashSet()
    aggregateRoles.addAll (groupRoles.flatten())
    aggregateRoles.addAll (individualRoles.flatten())
    aggregateRoles

所以一切看起来都很明智,并且做了我想要的(我的测试显示了通过叠加分配的正确角色)。但是 GormUserDetailsS​​ervice 并没有预料到这一点,并且好像 useGroups 是真的一样中断了它取消引用两个集合以获取一个集合,并使用它来构建 SimpleGrantedAuthority 集合。

结果是我被我试图做的事情塞满了,它与假设的模型相去甚远。

我会在 git 网站上将其作为建议,因为它会更加干净且不透明,而且我认为您直观地假设用户上的 authorities 属性会给您一个 Set。

现在只需要坚持基本的用户/组/角色模型并接受它。

【讨论】:

以上是关于使用用户组和角色时 Grails / Spring Security 中的错误 - 无法进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

Grails 和 Spring Security 插件:基于角色登录时重定向用户

Grails 1.3.5 和 Spring Security Core

Grails + spring-security-core:用户登录后如何分配角色?

Grails Spring Security Core 创建新用户

基于 SAML 响应在 Grails 中分配 Spring 安全角色

grails spring 安全角色和组