Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grails Spring Security AJAX responds with HTML of requested page, not JSON from ajaxSuccess()

So I have been playing with Grails' Spring Security, I'm having a great time but I'm stuck on one little thing regarding logging in via AJAX. I have a login form that posts to /j_spring_security_check via AJAX in order to log in, it is working very well, but I am puzzled by the different responses when I get to the login page 1) directly, 2) when I am redirected to the login page when I try to access a secured page. The login page is /login/auth, I replaced the standard form in auth.gsp with an AJAXified form. Here are the two situations:

  1. I browse to /login/auth directly, when I do this and log in with AJAX I get the standard JSON object back, that is I get something like {"success":true,"username":"charles"} or {"error":"Sorry, we were not able to find a user with that username and password."} - no surprises - all is well.
  2. Since the /login/auth is "the" log in page when I browse to another controller/action (like /users/list) in my app and I am not authenticated I get redirected to /login/auth, as expected. So when I enter in bogus credentials I get back {"error":"Sorry, we were not able to find a user with that username and password."}, as expected, but when I log in with correct credentials I get back the actual HTML of the originally requested page in the response body. Ideally, I would like to get back something like {"success":true,"username":"charles", referer:"/users/list"} - so that my login form can do cool animationy stuff then redirect to the originally requested URL. I see what's going on, in situation number 1 ajaxSuccess() in LoginController.groovy gets called (which returns the JSON) - but in this situation it does not get called, and I can't find out what does get called so that I can hack it to do what I want.

Is there a better way to do this? Or can somebody point me in the right direction? I would greatly appreciate any help!

Thanks a lot!

:)

Here's my AJAX call if it helps, although it is very standard:

<script>
$(document).ready(function(){
    $("#submit").click(function(){
        $("#response").html("Attempting login...");
        var formdata = $('#loginForm').serialize();
        $.ajax({
            url: "/UserDemo/j_spring_security_check",
            type: 'post',
            data: formdata,
            success: function(r){
                if (r.success) {
                    $("#response").html("Success!");
                } else if (r.error) {
                    $("#response").html(r.error);
                }
            }
        });
    });
});

like image 862
charlesread Avatar asked Nov 17 '13 01:11

charlesread


1 Answers

Basically the problem is that even for ajax requests, when coming from another url, Spring's SavedRequestAwareAuthenticationSuccessHandler redirects to the full page. You can see that in your Browser debugging tool, but you can't circumvent/prevent the redirect because of how ajax requests work (transparent redirects).

There might be a better way, but the following works. The redirect for ajax requests is changed like this:

Put a this class into source/groovy

class AjaxSuccessAwareRedirectStragegy extends DefaultRedirectStrategy {

    private String _ajaxSuccessUrl;

    public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
        if (SpringSecurityUtils.isAjax(request)) {
            if  (url != _ajaxSuccessUrl) {
                url = _ajaxSuccessUrl + "?targetUrl=" + URLEncoder.encode(url, 'UTF-8')
            }
            super.sendRedirect(request, response, url)
        } else {
            super.sendRedirect(request, response, url)
        }
    }

    public void setAjaxSuccessUrl(final String ajaxSuccessUrl) {
        _ajaxSuccessUrl = ajaxSuccessUrl;
    }
}

This special redirect strategy will then be used on the *AuthenticationSuccessHandler. In order to register it, put this configuration into resources.groovy (the AjaxSuccessAwareRedirectStragegy is the one relevant for exactly this use case)

beans = {

    def conf = SpringSecurityUtils.securityConfig

    authenticationSuccessHandler(AjaxAwareAuthenticationSuccessHandler) {
        //borrowed from DefaultSecurityConfig.groovy
        requestCache = ref('requestCache')
        defaultTargetUrl = conf.successHandler.defaultTargetUrl // '/'
        alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault // false
        targetUrlParameter = conf.successHandler.targetUrlParameter // 'spring-security-redirect'
        ajaxSuccessUrl = conf.successHandler.ajaxSuccessUrl // '/login/ajaxSuccess'
        useReferer = conf.successHandler.useReferer // false
        redirectStrategy = ref('ajaxSuccessAwareRedirectStrategy')
    }

    ajaxSuccessAwareRedirectStrategy(AjaxSuccessAwareRedirectStragegy) {
        contextRelative = conf.redirectStrategy.contextRelative
        ajaxSuccessUrl = conf.successHandler.ajaxSuccessUrl
    }

}

And voila, in your LoginController you can change the ajaxSuccess action to use the targetUrl, which in turn can then be used in your login Javascript

def ajaxSuccess = {
    def redirect = params.targetUrl
    println "LoginControllerAjaxAuthParams " + params
    render([success: true, username: springSecurityService.authentication.name] << (redirect ? [redirect: redirect] : [:]) as JSON)
}
like image 178
user3391859 Avatar answered Oct 21 '22 14:10

user3391859