Search code examples
grailsauthenticationspring-security

Grails - Intercepting entered Username in LoginController's authfail


I'm trying to force a user to change his password on the first login (as has been discussed here Grails spring-security - Can I intercept before success action to check required password change?).

However, neither UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY (which is also deprecated) nor springSecurityService.authentication.name does provide the entered username. All I get is __grails.anonymous.user__ what can't be used to set the user's new password.

Does anyone of you have an idea how to determine the entered username?

Thank you very much.


Solution

  • I didn't figure out how to pass on the username entered in the login form to the changePassword.gsp. Nevertheless, I constructed a workaround which is not perfect yet but at least is working.

    The authfail closure in my custom login controller stays as has been presented in Grails spring-security - Can I intercept before success action to check required password change?

    However, I deleted the changePassword closure because I added a textfield in the changePassword.gsp to enable the user to enter his username. That's also the reason why I think that this solution is not perfect, because normally I would not like to have to re-enter my username as well.

    As I'm quite new to grails, some of you may have a hint or two on how to realize this functionality more elegant. I would really appreciate it :)

    Following the source extracts for LoginController and changePassword.gsp.

    LoginController.groovy:

    /**
     * Callback after a failed login. Redirects to the auth page with a warning message.
     */
    def authfail() {
    
        String msg = ''
        def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]
        if (exception) {
            if (exception instanceof AccountExpiredException) {
                msg = g.message(code: "springSecurity.errors.login.expired")
            }
            else if (exception instanceof CredentialsExpiredException) {
                msg = g.message(code: "springSecurity.errors.login.passwordExpired")
                if (!springSecurityService.isAjax(request)) {
                    flash.message = msg
                    render view: 'changePassword'
                    return
                }
            }
            // omitting other cases
        }
    
        if (springSecurityService.isAjax(request)) {
            render([error: msg] as JSON)
        }
        else {
            flash.message = msg
            redirect action: 'auth', params: params
        }
    }
    
    def updatePassword() {
        String username = params.username
    
        if (!username) {
            flash.message = 'Sorry, an error has occurred'
            redirect controller: 'login', action:'auth'
            return
        }
    
        String password = params.password_old
        String newPassword = params.password_new
        String newPassword2 = params.password_new_rep
    
        if (!password || !newPassword || !newPassword2 || newPassword != newPassword2) {
            flash.message = 'Please enter your current password and a new password'
            render view: 'changePassword', model: [username: username]
            return
        }
    
        User user = userService.getUserByUsername(username)
    
        if (!passwordEncoder.isPasswordValid(user.password, password, null /*salt*/)) {
            flash.message = 'Current password is incorrect'
            render view: 'changePassword', model: [username: username]
            return
        }
    
        if (passwordEncoder.isPasswordValid(user.password, newPassword, null /*salt*/)) {
            flash.message = 'Please choose a different password from your current one'
            render view: 'changePassword', model: [username: username]
            return
        }
    
        if (user.username.equals(newPassword)) {
            flash.message = 'New password must not equal your username'
            render view: 'changePassword', model: [username: username]
            return
        }
    
        // success if we reach here!
        user.password = newPassword
        user.passwordExpired = false
        userService.updateUser(user)
    
        springSecurityService.reauthenticate(username, newPassword)
    
        flash.message = 'Password changed successfully' + (springSecurityService.loggedIn ? '' : ', you can now login')
        redirect uri: '/'
    }
    

    changePassword.gsp:

    <html>
        <head>
            <meta name="layout" content="general" />
            <title>Change password</title>
        </head>
        <body>
            <content tag="heading">Change password</content>
            <content tag="content">
                <g:if test="${flash.message }">
                    ${flash.message }
                </g:if>
                <g:form name="changePassword" autocomplete="off">
                <table>
                    <tr>
                        <td>Username</td>
                        <td><g:field type="text" name="username" required="" /></td>
                    </tr>
                    <tr>
                        <td>Old password</td>
                        <td><g:field type="password" name="password_old" required=""/></td>
                    </tr>
                    <tr>
                        <td>New password</td>
                        <td><g:field type="password" name="password_new" required=""/></td>
                    </tr>
                    <tr>
                        <td>New password (repeat)</td>
                        <td><g:field type="password" name="password_new_rep" required=""/></td>
                    </tr>
                    <tr>
                        <td></td>
                        <td>
                            <g:actionSubmit name="submit" value="Change password" action="updatePassword"/>
                        </td>
                    </tr>
    
                </table>
                </g:form>
            </content>
        <script>
    $(document).ready(function() {
        $('#username').focus();
    });
        </script>
        </body>
    </html>