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

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


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


I believe I have an answer which could help you out. I've been toying around with this quite a bit lately, and here's the solution I came up with. I'm sure it's not perfect, but it's been working out nicely for me.

First, I had to figure out why the Grails Spring Security plugin was not redirecting to the ajaxSuccess action in my LoginController, as expected (see the Spring Security Plugin Docs). I discovered that the plugin is looking for a header or for a query param (see the Spring Security Plugin source code on github. Posting my credentials to this URI, I'm able to get the redirect to go to the correct action in the controller: /j_spring_security_check?ajax=true

Second, I changed the implementation of the ajaxSuccess action in my LoginController.groovy. You can make this return whatever you need to support your own implementation.

/** * The Ajax success redirect url. */def ajaxSuccess(){    def currentUser = springSecurityService.principal    [success: true, user: [id:currentUser.id, fullName:currentUser.fullName, confirmed:currentUser.confirmed, enabled:currentUser.enabled]]}

Third, I wanted to use the 'Accept' HTTP header to determine when to return json, and when to let the controller render GSP views. I created a Grails filter, which looks for a few params or headers to determine when to "jsonify" my results. The general idea is that I will take the model return from the controller and render that as json (or as jsonp). I believe that I took this concept from a plugin called jsonify, or something similar. Here's the code I'm using in my filter:

jsonify(controller: '*', action: '*') {        after = { Map model ->            String acceptHeader = request.getHeader('accept')            boolean isAcceptHeaderApplicationJson = "application/json".equals(acceptHeader)            boolean isJsonRequest = 'json'.equalsIgnoreCase(params.format?.trim()) || isAcceptHeaderApplicationJson            boolean isJsonpRequest = StringUtils.isNotBlank(params.callback)            if(isJsonRequest != true && isJsonpRequest != true){                return true            }            // check if we can unwrap the model (such in the case of a show)            def modelToRenderAsJson = model?.size() == 1 ? model.find { true }.value : model            if(isJsonpRequest){                response.setContentType('application/javascript;charset=utf-8')                render "${params.callback}(${modelToRenderAsJson as JSON})"            }else{                response.setContentType('application/json;charset=utf-8')                render modelToRenderAsJson as JSON            }            return false        }    }

Finally, I'm able to test this out using my running Grails application, and the Google Chrome plugin called Postman. I'm including a screenshot of my Postman config in hopes that it will help.

Postman Ajax login screenshot


I faced a similar issue. My login page used to redirect to a page which used to render some json(instead of the root page defined in URLMappgings). My controller action was being called in the ajax request.

I simply applied a setTimeout function so that on page load, the ajax call does not get fired immediately.This solution works as long as your ajax call only renders a response(and no redirects).

Here is the code snippet:

function isRunning() {            $.ajax({                type: "GET",                url: "${g.createLink(controller:'globalSchedule',action:'isJobRunning')}",                dataType:"json",                success: function(data,textStatus){                    if (data.status) {                        $("#refreshOrdersLoadingImage").show();                    } else {                        $("#refreshOrdersLoadingImage").hide();                    }                }            });        }//Now Applying the setTimeout so that on page load, the ajax immediately does not fire        $(function(){setTimeout(function () {            setInterval(isRunning, 15000);        },10000)});