On a recent project at work, I was tasked with porting a web app (or at least certain views of the UI) to a mobile device friendly UI. For whatever reason, the mandate was to make it "slick" on an iPhone. Since the project already uses jQuery extensively, it naturally made sense to use a framework that is compatible with jQuery, and mobile device. I chose (for a prototype) jQtouch, as jQuery mobile isn't slated for release until end of year.
Too many pros to talk about this framework it kicks ass. Unfortunately, looks like everything runs on a single (index.*) page, with separate divs which forms the panels.
My first hiccup using jQtouch was the authentication process. I'd hoped to be able to re-use ALL of our customized authentication code. We use Spring Security, along with some Roll-Your-Own UserdDetailsService. After some research, I determined that I could run multiple Authentication Processing filters on Spring Security's filter chain. Here's how I made the whole thing work using an ajaxified login form with Spring's UsernamePasswordAuthenticationFilter, and LogoutFilter.
first the application-context beans required:
Then, I added these beans to the existing Spring Security Configuration's filter chain (pay specific to the last 2 lines);
Don't forget to update the web.xml with the new filters;
mobileAuthenticationProcessingFilter
org.springframework.web.filter.DelegatingFilterProxy
mobileAuthenticationProcessingFilter
/*
mobileAuthenticationInvalidationFilter
org.springframework.web.filter.DelegatingFilterProxy
mobileAuthenticationInvalidationFilter
/*
Since I don't want to redirect after login, I need to override the default behaviour in the MobileAuthenticationProcessingFilter:
public class MobileAuthenticationProcessingFilter extends UsernamePasswordAuthenticationFilter {
private static Logger log = Logger.getLogger(MobileAuthenticationProcessingFilter.class);
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, authResult);
HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(response);
responseWrapper.setContentType("text/json");
String json = "{authenticated:true, navTag : \"" + request.getRequestURI() + "#Crew \"}";
log.debug("Successful Authentication, writing JSON success Response: " + json );
renderResponse(responseWrapper, json);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
/*
* Hacky, but works. What's happening is that the normal "onAuthenticationFailure" method
* is sending a redirect, which is screwing up the ajax call to authenticate. Inb this case,
* we'll let the AbstractAuthenticationFilter populate the session scope with the EXCEPTION_KEY,
* but then gracefully STFU.
*/
super.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// do nothing
}
});
super.unsuccessfulAuthentication(request, response, failed);
HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(response);
String failureReason = request.getSession().getAttribute(SPRING_SECURITY_LAST_EXCEPTION_KEY) != null ?
((Exception)request.getSession().getAttribute(SPRING_SECURITY_LAST_EXCEPTION_KEY)).getMessage() :
"Invalid login attempt, check your authentication credentials.";
responseWrapper.setContentType("text/json");
String json = "{ authenticated: false,\"errors\": { \"reason\": \" " + failureReason + " \"} }";
log.debug("Failed Authentication, writing JSON failure Response: " + json);
renderResponse(responseWrapper, json);
}
private void renderResponse(HttpServletResponseWrapper responseWrapper, String json) throws IOException {
Writer out = responseWrapper.getWriter();
out.write(json);
out.close();
}
}
Additionally, if you noticed in the application-context bean we provided the com.denlab.web.filter.MobileLogoutSuccessHandler class as a constructor arg, which also overrides the behaviour of the default:
public class MobileLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//Do Nothing, we'll write a response later.
Logger.getLogger(this.getClass()).debug("Logging out");
}
}
Now that we've configured spring, implemented our classes, all that's left is to provide the login form, inside the index.jsp, with Spring's default params:
And a logout button:
Logout
Use jQuery to intercept the form submission, and fire the ajax event:
When the principal authenticates, the MobileAuthenticationProcessingFilter will prevent the response redirect and instead write a json object directly to the response.