Thursday, December 16, 2010

Google App Engine for Java and the SSL Session

If you are building an application on Google App Engine for Java and you use sessions, SSL, and a custom domain name, you may have ran into trouble transferring the session between the secure and non-secure portions of your site. This is because SSL on App Engine requires use of the appspot.com domain while the rest of your application is accessed by your own domain name. When a browser accesses one domain, the session is established by a cookie that is inaccessible to the other. This presents much difficulty if you are designing something like a login or registration form that must be secure while later redirecting to an insecure page that needs the same session to know if the user is authenticated.

A simple solution is to transfer the jsessionid cookie value to the insecure site after you manipulate the session on the secure side. The flow would look something like this in the example of a login process:
  • User requests secure page for login form.
  • Submits form by POST over SSL to servlet or other handler
  • In the servlet, update the session as you desire but also grab the jsessionid cookie
  • Redirect to an insecure servlet that accepts the jsessionid as a parameter
  • Write the received jsessionid cookie back to the response from the insecure side
  • Redirect or render appropriate view
The insecure session has now been replaced with the one established on the secure page. Login complete!

Since my application is built in Spring 3 MVC, I have some sample code readily available that would go something like this:

First, the controller method that handles the post of the login form has to grab the jsessionid from the current secure session. Remember, this post is done to the https appspot url.

@RequestMapping(method = RequestMethod.POST)
public String doSubmit(@ModelAttribute("loginForm") @Valid LoginForm loginForm
  , @CookieValue("JSESSIONID") String jsessionid) {

  //do the login logic
  Long userId = userService.doLogin(loginForm.getEmail(), loginForm.getPassword());

  if (userId != null ) {

    //....
    //set some kind of session variable here to indicate the user is authenticated
    //this variable will be accessible from any insecure page
    //....

    return "redirect:http://www.mydomain.com/session-transfer.html?jsessionid="+jsessionid+"&destination=%2F";
  }
}


Notice the jsession id being taken from the current session and passed as a url parameter to the url that will actually transfer the session to the insecure site. The handler for the session-transfer url would look like this:

@RequestMapping(method = RequestMethod.GET)
public String handleRequest(@RequestParam("jsessionid") String jsessionId
  , @RequestParam("destination") String destination
  , HttpServletResponse response) {

  //overwrite jsessionid from the one from the ssl site
  Cookie jsessionCookie = new Cookie("JSESSIONID", jsessionId);
  response.addCookie(jsessionCookie);
  return "redirect:" + destination;
}


Since this handler is happening in the context of the http site, the current jsessionid the browser is providing in its cookie is for the insecure session. Here, we overwrite that jsessionid with the one from the secure site where the session was updated to indicate the user is authenticated. Now, anywhere in the site that needs session variables from insecure pages can read those that were set during the secure session.