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:
/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./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);
}
}
});
});
});
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With