Grails openid 插件 2.0.0 RC1:得到“没有这样的属性:用户异常”

Posted

技术标签:

【中文标题】Grails openid 插件 2.0.0 RC1:得到“没有这样的属性:用户异常”【英文标题】:Grails openid plugin 2.0.0 RC1: getting "No such property: User exception" 【发布时间】:2013-11-06 15:24:55 【问题描述】:

已将openid plugin 2.0.0 RC1 安装到我的 grails 应用程序中,但我得到了 没有这样的属性:用户异常。有人可以建议应该做什么吗?

| Error 2013-10-28 00:11:55,169 [http-bio-8080-exec-6] ERROR errors.GrailsExceptionResolver  - MissingPropertyException occurred when processing request: [POST] /Test/login/openIdCreateAccount - parameters:
username: valid_gmail@gmail.com
password2: ***
password: ***
No such property: User for class: grails.plugin.springsecurity.openid.OpenIdRegisterCommand. Stacktrace follows:
Message: No such property: User for class: grails.plugin.springsecurity.openid.OpenIdRegisterCommand
   Line | Method
->> 265 | doCall   in grails.plugin.springsecurity.openid.OpenIdRegisterCommand$__clinit__closure1_closure2
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   195 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|    63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
|    53 | doFilter in grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter
|    49 | doFilter in grails.plugin.springsecurity.web.authentication.RequestHolderAuthenticationFilter
|    82 | doFilter in grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter
|   895 | runTask  in java.util.concurrent.ThreadPoolExecutor$Worker
|   918 | run      in     ''
^   695 | run . .  in java.lang.Thread

【问题讨论】:

您的应用中是否安装了Spring Security Core plugin?如果答案是否定的,那么RTFM。 嗨,是的,它已安装。当我在 gmail 身份验证后被重定向并尝试为给定的 openid 注册新用户时,我遇到了异常。 【参考方案1】:

这个bug在RC2中没有修复,我两天前安装了它,遇到了同样的问题。实际上,OpenIdController 中有 4 个错误。我将在下面的建议答案中提到这些错误的详细信息以及 user2576874 修复的解决方案:

1:第 267 行,类引用了 OpenIdRegistrationCommand 的静态闭包中的 grailsApplication bean。这里给出的解决方案是将grailsApplication替换为:grails.util.Holders.getGrailsApplication()

2:第 286 行,当我们在 OpenId 登录回调后尝试注册用户时,regEx 失败。这是一个更简单的正则表达式条件:

(!password.matches('^.*[a-zA-Z].*$') ||
!password.matches('^.*[0-9].*$') ||
!password.matches('^.*[!@#$%^&].*$')))

3:第 183 行,字段名称有错误:usernamePropertyName。正确的字段名称是usernamePropName,如第 49 行所述。

4:运行代码会抛出 NullPointerException,因为 Role 类未定义类型。解决方案是将类类型添加到与 User 和 UserRole 定义相同的变量 Role 中。在 OpenIdConroller 的第 255 行之后添加了以下代码:

Role = grailsApplication.getClassForName(conf.authority.className)

谢谢

【讨论】:

【参考方案2】:

这是对我有用的版本。您能否在此基础上从插件中发布一个新的 RC3?

/* Copyright 2013 SpringSource.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package grails.plugin.springsecurity.openid

import grails.plugin.springsecurity.SpringSecurityUtils
import grails.plugin.springsecurity.annotation.Secured

import org.springframework.beans.factory.InitializingBean
import org.springframework.orm.hibernate3.SpringSessionContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.AuthenticationException

/**
 * Manages associating OpenIDs with application users, both by creating a new local user
 * associated with an OpenID and also by associating a new OpenID to an existing account.
 */
@Secured('permitAll')
class OpenIdController implements InitializingBean 

    /** Dependency injection for daoAuthenticationProvider. */
    def daoAuthenticationProvider

    /** Dependency injection for the grailsApplication. */
    def grailsApplication

    /** Dependency injection for OpenIDAuthenticationFilter. */
    def openIDAuthenticationFilter

    /** Dependency injection for the requestCache. */
    def requestCache

    /** Dependency injection for the springSecurityService. */
    def springSecurityService

    private Class User
    private Class UserRole
    private Class Role
    private String usernamePropName
    private String passwordPropertyName
    private String enabledPropertyName
    private String roleNameField

    static defaultAction = 'auth'
    static scope = 'singleton'

    /**
     * Shows the login page. The user has the choice between using an OpenID and a username
     * and password for a local account. If an OpenID authentication is successful but there
     * is no corresponding local account, they'll be redirected to createAccount to create
     * a new account, or click through to linkAccount to associate the OpenID with an
     * existing local account.
     */
    def auth() 

        def config = SpringSecurityUtils.securityConfig

        if (springSecurityService.isLoggedIn()) 
            redirect uri: config.successHandler.defaultTargetUrl
            return
        

        [openIdPostUrl: "$request.contextPath$openIDAuthenticationFilter.filterProcessesUrl",
         daoPostUrl:    "$request.contextPath$config.apf.filterProcessesUrl",
         persistentRememberMe: config.rememberMe.persistent,
         rememberMeParameter: config.rememberMe.parameter,
         openidIdentifier: config.openid.claimedIdentityFieldName]
    

    /**
     * Initially we're redirected here after a UserNotFoundException with a valid OpenID
     * authentication. This action is specified by the openid.registration.createAccountUri
     * attribute.
     * <p/>
     * The GSP displays the OpenID that was received by the external provider and keeps it
     * in the session rather than passing it between submits so the user has no opportunity
     * to change it.
     */
    def createAccount(OpenIdRegisterCommand command) 

        String openId = session[OpenIdAuthenticationFailureHandler.LAST_OPENID_USERNAME]
        if (!openId) 
            flash.error = 'Sorry, an OpenID was not found'
            return [command: command]
        

        if (!request.post) 
            // show the form
            command.clearErrors()
            copyFromAttributeExchange command
            return [command: command, openId: openId]
        

        if (command.hasErrors()) 
            return [command: command, openId: openId]
        

        if (!createNewAccount(command.username, command.password, openId)) 
            return [command: command, openId: openId]
        

        authenticateAndRedirect command.username
    

    /**
     * The registration page has a link to this action so an existing user who successfully
     * authenticated with an OpenID can associate it with their account for future logins.
     */
    def linkAccount(OpenIdLinkAccountCommand command) 

        String openId = session[OpenIdAuthenticationFailureHandler.LAST_OPENID_USERNAME]
        if (!openId) 
            flash.error = 'Sorry, an OpenID was not found'
            return [command: command]
        

        if (!request.post) 
            // show the form
            command.clearErrors()
            return [command: command, openId: openId]
        

        if (command.hasErrors()) 
            return [command: command, openId: openId]
        

        try 
            registerAccountOpenId command.username, command.password, openId
        
        catch (AuthenticationException e) 
            flash.error = 'Sorry, no user was found with that username and password'
            return [command: command, openId: openId]
        

        authenticateAndRedirect command.username
    

    /**
     * Authenticate the user for real now that the account exists/is linked and redirect
     * to the originally-requested uri if there's a SavedRequest.
     *
     * @param username the user's login name
     */
    protected void authenticateAndRedirect(String username) 
        session.removeAttribute OpenIdAuthenticationFailureHandler.LAST_OPENID_USERNAME
        session.removeAttribute OpenIdAuthenticationFailureHandler.LAST_OPENID_ATTRIBUTES

        springSecurityService.reauthenticate username

        def config = SpringSecurityUtils.securityConfig
        def savedRequest = requestCache.getRequest(request, response)
        if (savedRequest && !config.successHandler.alwaysUseDefault) 
            redirect url: savedRequest.redirectUrl
        
        else 
            redirect uri: config.successHandler.defaultTargetUrl
        
    

    /**
     * Create the user instance and grant any roles that are specified in the config
     * for new users.
     * @param username  the username
     * @param password  the password
     * @param openId  the associated OpenID
     * @return  true if successful
     */
    protected boolean createNewAccount(String username, String password, String openId) 
        boolean created = User.withTransaction  status ->
            def config = SpringSecurityUtils.securityConfig

            password = encodePassword(password)
            def user = User.newInstance((usernamePropName): username,
                                        (passwordPropertyName): password,
                                        (enabledPropertyName): true)

            user.addToOpenIds(url: openId)

            if (!user.save()) 
                return false
            

            for (roleName in config.openid.registration.roleNames) 
                UserRole.create user, Role.findWhere((roleNameField): roleName)
            
            return true
        
        return created
    

    protected String encodePassword(String password) 
        def config = SpringSecurityUtils.securityConfig
        def encode = config.openid.encodePassword
        if (!(encode instanceof Boolean)) encode = false
        if (encode) 
            password = springSecurityService.encodePassword(password)
        
        password
    

    /**
     * Associates an OpenID with an existing account. Needs the user's password to ensure
     * that the user owns that account, and authenticates to verify before linking.
     * @param username  the username
     * @param password  the password
     * @param openId  the associated OpenID
     */
    protected void registerAccountOpenId(String username, String password, String openId) 
        // check that the user exists, password is valid, etc. - doesn't actually log in or log out,
        // just checks that user exists, password is valid, account not locked, etc.
        daoAuthenticationProvider.authenticate new UsernamePasswordAuthenticationToken(username, password)

        User.withTransaction  status ->
            def user = User.findWhere((usernamePropName): username)
            user.addToOpenIds(url: openId)
            if (!user.validate()) 
                status.setRollbackOnly()
            
        
    

    /**
     * For the initial form display, copy any registered AX values into the command.
     * @param command  the command
     */
    protected void copyFromAttributeExchange(OpenIdRegisterCommand command) 
        List attributes = session[OpenIdAuthenticationFailureHandler.LAST_OPENID_ATTRIBUTES] ?: []
        for (attribute in attributes) 
            // TODO document
            String name = attribute.name
            if (command.hasProperty(name)) 
                command."$name" = attribute.values[0]
            
        
    

    void afterPropertiesSet() throws Exception 
        def conf = SpringSecurityUtils.securityConfig
        usernamePropName = conf.userLookup.usernamePropertyName
        passwordPropertyName = conf.userLookup.passwordPropertyName
        enabledPropertyName = conf.userLookup.enabledPropertyName
        roleNameField = conf.authority.nameField
        User = grailsApplication.getClassForName(conf.userLookup.userDomainClassName)
        UserRole = grailsApplication.getClassForName(conf.userLookup.authorityJoinClassName)
        Role = grailsApplication.getClassForName(conf.authority.className)
    


class OpenIdRegisterCommand 

    String username = ""
    String password = ""
    String password2 = ""

    static constraints = 

        username blank: false, validator:  String username, command ->

            def User = grails.util.Holders.getGrailsApplication().getClassForName(SpringSecurityUtils.securityConfig.userLookup.userDomainClassName)

            User.withNewSession  session ->
                if (username) 
                    boolean exists = User.createCriteria().count 
                        eq SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName, username
                    
                    if (exists) 
                        return 'openIdRegisterCommand.username.error.unique'
                    
                
            
        
        password blank: false, minSize: 8, maxSize: 64, validator:  password, command ->
            if (command.username && command.username.equals(password)) 
                return 'openIdRegisterCommand.password.error.username'
            

            if (password && password.length() >= 8 && password.length() <= 64 &&
                    (!password.matches('^.*[a-zA-Z].*$') ||
                    !password.matches('^.*[0-9].*$') ||
                    !password.matches('^.*[!@#$%^&].*$'))
                ) 
                return 'openIdRegisterCommand.password.error.strength'
            

        
        password2 validator:  password2, command ->
            if (command.password != password2) 
                return 'openIdRegisterCommand.password2.error.mismatch'
            
        
    


class OpenIdLinkAccountCommand 

    String username = ""
    String password = ""

    static constraints = 
        username blank: false
        password blank: false
    

【讨论】:

【参考方案3】:

这是一个错误,我修复了它并发布了 2.0-RC2。请更新到该版本,看看它是否有效。

【讨论】:

嗨,我更新了,但现在 grailsApplication 出现同样的错误。看起来 OpenIdRegisterCommand 在闭包中没有看到来自父类的 grailsApplication 在这一行:def User = grailsApplication.getClassForName(SpringSecurityUtils.securityConfig.userLookup.userDomainClassName) 您好,能否请您发布一个新版本 RC3,并提供解决方案?

以上是关于Grails openid 插件 2.0.0 RC1:得到“没有这样的属性:用户异常”的主要内容,如果未能解决你的问题,请参考以下文章

Grails 2.0.0.RC3:无法重新加载对控制器的更改,java.lang.NoSuchFieldException:__timeStamp__239_neverHappen1323622798

grails 2.4.0.M1 中的 spring 安全插件出错

Grails 3 数据库迁移问题

使用 grails 安全插件时出错

为啥要将 grails 操作声明为方法而不是闭包,它有啥区别?

使用 Spring Security Grails 插件编码密码