Spring Security & Grails: Cross domain authentication from HTTP to HTTPS
We were trying to implement SSL-based login and registration (i.e. HTTPS) in an e-commerce web application which was otherwise using the non-secure protocol (i.e. HTTP) for the entire website.
Instead of moving the entire web application to SSL, which would have increased response times, we thought it would be best if only the authentication part of the web application could be moved to be SSL. However, this also posed an even bigger problem of cross-domain authentication since the protocol was shifting from non-secure (HTTP) to secure pages (HTTPS).
As you all know, Spring Security uses an internal URL (/j_spring_security_check) for authentication purposes that is provided out of the box. So our first attempt at cross-domain authentication was via AJAX. But we soon gave up that solution because of how cross-domain operates in the real world.
How cross domain requests operate
Whenever you’re making a cross-domain AJAX call, the following happens:
- A pre-flight request is sent first (an OPTIONS request) to ensure that the domain making the request is allowed to make the request to the domain receiving the request.
- If the receiving domain sends back a response header containing the “Access-Control-Allow-Origin”, then the subsequent GET/POST request is sent and the normal flow starts.
Now Grails doesn’t support a CORS request out-of-the-box according to this issue. So we have a wonderful plugin to do just that. But it doesn’t seem to be able to add CORS headers to the Spring Security plugin. So I had to give up the AJAX-ified approach and started looking towards an iframe based approach.
1. Change the login form
[code]
<form action="https://localhost:8443/j_spring_security_check" method="POST" name="loginForm" target="hidden_iframe">
<input type="text" name="j_username" />
<input type="text" name="j_password" />
</form>
<iframe style="display: none;" name="hidden_iframe" width="320" height="240"></iframe>
[/code]
What the above code does is that it sends a POST request to the requested URL and loads the response in the iframe instead of redirecting the page.
2. Change the Spring Security plugin config
We were using the wonderful Spring Security plugin for authentication which is highly configurable. We changed the authentication success and failure URL’s to redirect to our custom GSP’s.
[code]
grails.plugins.springsecurity.failureHandler.ajaxAuthFailUrl = ‘/login/denied’
grails.plugins.springsecurity.successHandler.defaultTargetUrl = ‘/login/authsuccess’
[/code]
3. Change content of required GSP’s
denied.gsp
[js]<script type="text/javascript">// <![CDATA[
window.top.postMessage( JSON.stringify({signin: ‘error’}), "*" );
// ]]></script>
[/js]
auth.gsp
[js]<script type="text/javascript">// <![CDATA[
window.top.postMessage( JSON.stringify({signin: ‘success’}), "*" );
// ]]></script>
[/js]
The postMessage method is a special Javascript method for cross-iframe communication. The first argument is the data that needs to be posted, and the second argument is the domain to which the data should be sent. When you specify “*”, it means it will be sent to all domains and can be caught by any event listener that can listen to the event generate.
4. Modify page containing the login form
Add the following piece of javascript to catch the event that’s being generated by the hidden iframe.
The code is being picked up from David Walsh’s blog to provide cross-browser event compatibility. Thanks David!
[code]
// Create IE + others compatible event handler
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
// Listen to message from child window
eventer(messageEvent,function(e) {
console.log(‘parent received message!: ‘, JSON.parse(e.data));
// Do additional processing of data received
},false);
[/code]
And there you are. You have a perfectly working cross-domain authentication mechanism in place that’s integrated with Grails, Spring Security, and JavaScript.
Awsome article and гight to the poіnt. I am not sᥙre if
tһis іs гeally tһe best plаce to ask Ьut Ԁo уou folks
hɑve any ideea wһere to employ ѕome professional writers?
Ƭhank you 🙂
This site was… how do you say it? Relevant!!
Finally I’ve found something which helped me. Cheers!
Thanks Sitati for the feedback! Much appreciated! The links you have provided are very helpful and I agree with the points you have raised and would like to comment on a few of them.
The performance hit was a huge decision maker to not shift the entire website to HTTPS. Our application relies heavily on AJAX calls and even an average increase of 100ms per call would be a huge performance hit to the end user response times and user experience.
Another thing which led us to not move to HTTPS was that the entire setup would have to be tested to ensure that the website functions as expected on the new HTTPS site, which in itself is a huge task. We were not sure if we were ready to devote that much amount of time and effort to this task at that particular moment. We decided against moving to HTTPS for this reason and tried to get other more important features finished.
Hey, nice article, and I follow your blog closesly, being a Grails dev myself.
Was pretty surprised by the decision to do SSL only on parts of your website though, and looking around for discussions on the topic I found this SO post – http://stackoverflow.com/questions/1468648/https-vs-http-speed-comparison
“Speed should not be a factor in deciding whether to use HTTPS or HTTP. If you need HTTPS for any part of your site (log-ins, registration, credit cards, etc), you absolutely need HTTPS for all of it, all the time.”
The article linked to from that discussion states that it’s mainly a trust thing (users like padlocks in their omniboxes), but furthermore, passing authentication cookies over http leaves you open to man-in-the-middle attacks: http://stackoverflow.com/questions/3780582/only-use-ssl-on-login-or-whole-site
Were these not considerations in your case? And did you measure SSL performance beforehand, i.e. was the speed consideration so major as to justify overlooking these other areas?