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

So, I played with Grails Spring protection, I have a great time, but I was stuck on one small thing related to logging in through AJAX. I have a login form that submits to /j_spring_security_check via AJAX to log in, it works very well, but I am amazed at the various answers when I get to the login page 1), 2) when I am redirected to the login page, when I am trying to access a secure page. Login page /login/auth , I replaced the standard form in auth.gsp with the auth.gsp form. Here are two situations:

  • I look through /login/auth directly when I do this and log in with AJAX. I return a standard JSON object, 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.
  • Since /login/auth is a "log" page, when I look at another controller / action (like /users/list ) in my application and I am not authenticated, I redirect to /login/auth , as expected. So when I enter the dummy credentials, I return {"error":"Sorry, we were not able to find a user with that username and password."} , As expected, but when I log in with the correct credentials By data, I return the actual HTML of the originally requested page in the response body. Ideally, I would like to return something like {"success":true,"username":"charles", referer:"/users/list"} so that my login form can create cool animations and then redirect to the original the requested URL. I see what happens, in situation number 1 ajaxSuccess() in LoginController.groovy is called (which returns JSON) - but in this situation it is not called, and I can’t find out what is called so that I can crack it to do what I want.

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

Thanks a lot!

:)

Here is 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); } } }); }); }); 

+6
source share
4 answers

Basically the problem is that even for ajax requests, when navigating from another Spring URL, SavedRequestAwareAuthenticationSuccessHandler is redirected to the full page. You can see this in your browser debugging tool, but you cannot bypass / prevent redirection due to the operation of ajax requests (transparent redirects).

There may be a better way, but the following works. Redirecting to ajax requests changes as follows:

Put this class in / groovy source code

 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 redirection strategy will be used in * AuthenticationSuccessHandler. To register it, enter this configuration in .groovy resources (AjaxSuccessAwareRedirectStragegy is the one that is suitable specifically for 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 targetUrl, which in turn can be used in your Javascript to login

 def ajaxSuccess = { def redirect = params.targetUrl println "LoginControllerAjaxAuthParams " + params render([success: true, username: springSecurityService.authentication.name] << (redirect ? [redirect: redirect] : [:]) as JSON) } 
+3
source

I believe that I have an answer that could help you. I've been doing this a lot lately, and here is the solution I came up with. I'm sure it is not perfect, but it works great for me.

First, I had to find out why the Grails Spring Security plugin was not redirected to the ajaxSuccess action in my LoginController, as expected (see Spring Security Plugin Docs ). I found that the plugin is looking for a header or request parameter (see Spring Security plugin source code on github . Credentials for this URI, I can get a redirect to go to the correct action in the controller: / j_spring_security_check? Ajax = true

Secondly, I changed the implementation of the ajaxSuccess action in my LoginController.groovy. You can do this by returning what 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 allow the controller to display GSP views. I created a Grails filter that looks for several parameters or headers to determine when my results are "jsonify". The general idea is that I will take the return model from the controller and do it as json (or as jsonp). I believe that I took this concept from a plugin called jsonify or something similar. Here is the code that I use 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 can verify this using my Grails app and a Google Chrome plugin called Postman. I included a screenshot of my Postman configuration in the hope that this will help.

Postman Ajax login screenshot

0
source

I ran into a similar problem. My login page was used to redirect to the page used to render some json (instead of the root page defined in URLMappgings). My controller action was called in an ajax request.

I just applied the setTimeout function so that when the page loads, the ajax call does not start immediately. This solution works as long as your ajax call displays the answer (and doesn't redirect).

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)}); 
0
source

User input3391859, targetUrl does not work. But with this fix, this worked for me:

 class AjaxSuccessAwareRedirectStrategy extends DefaultRedirectStrategy { private RequestCache requestCache = new HttpSessionRequestCache(); private String _ajaxSuccessUrl; public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException { SavedRequest savedRequest = this.requestCache.getRequest(request, response) String targetUrl = savedRequest ? savedRequest.getRedirectUrl() : "" if (SpringSecurityUtils.isAjax(request)) { //if (url != _ajaxSuccessUrl) { url = _ajaxSuccessUrl + "?targetUrl=" + URLEncoder.encode(targetUrl, 'UTF-8') //} super.sendRedirect(request, response, url) } else { super.sendRedirect(request, response, url) } } public void setAjaxSuccessUrl(final String ajaxSuccessUrl) { _ajaxSuccessUrl = ajaxSuccessUrl; } 

Which also fixes the AjaxSuccessAwareRedirectStragegy naming convention for AjaxSuccessAwareRedirectStrategy

0
source

Source: https://habr.com/ru/post/958239/


All Articles