Sunday, August 7, 2011

Spring security 3 Ajax login - accessing protected resources

I have seen some blogs about Spring Security 3 Ajax login, however I could not find any that tackles how to invoke Ajax based login, where a protected resource is being accessed in Ajax by an anonymous user.

The problem - The web application enables anonymous access to certain parts and certain parts are protected resources which require the user to login.
When an anonymous user accesses protected resources (via Http Get / Post), Spring Security automatically invokes the login page and after a successful authentication, redirects to the required resource/page.
However, if the protected resource is being accessed in Ajax, the login page will not appear correctly (will be set on part of the page). The 302 code (redirect to login page) will not function as expected in Ajax.
Please note that this is NOT the same as initiating an Ajax login screen (e.g. when user press on the login button and a popup with user/password fields is being invoked).
So - how can we have Spring Security 3 handle access to protected resources both with "regular" HTTP Post(FORM based authentication) AND Ajax calls, including a redirect to the required resource after successful authentication?

So, this blog post has two protection layers/parts:
1. Spring Security 3 standard FORM based authentication
2. Configure/extends Spring Security 3 and the app to support also Ajax access to protected resources.

Regarding part 1 - there are many references about the issue. No need to elaborate.
Regarding part 2 - Requires the following:
   1. Configure Spring Security 3 to enable Ajax based login.
   2. Configure client Ajax calls to protected resources to handle request for authentication.
   3. Re-execution of functions to simulate the automatic user original method invocation after successful login (as it happens in the FORM based login)

The below diagram describes a detailed flow and should help follow the client/sever communication.

Handling protected resource access via Ajax





Lets discuss the diagram:

The flow starts with an anonymous user Ajax request to a protected resource (1). In this case the user wants to add an item to the shopping cart.

The addItem method is a protected resource, which is protected via Spring Security (@pre_authorize("SOME_ROLE")) (2).  This causes the Spring Secutiry filter (3) to send the login FORM with HTTP code 302 (i.e. redirect to that page).

Now, since this is an Ajax call, it will not handle the request well, so here comes the part that takes the login FORM, put it aside, and invoke Ajax based login instead (4):

The client Ajax method (which invoked the Ajax addItem method) checks whether it is a form based login or any other reply. If it is a FORM based login, it will call a dialog modal (5) that will try to login in Ajax. Spring will handle the Ajax login authentication (6) and return an appropriate message to the client. The client, if the message was successful, will re-execute the original function, which tried to access the protected resource (e.g. addItem in our example).

Let us see how it all fits in our code:
Steps #1, #4 - Client side which accesses protected resources and checks if a login is required
//JavaScript method - Ajax call to protected resource (#1 in flow diagram)
function addItem(itemId) {    
    $.ajax({
        url: '/my_url/order/addItem',
        type: 'POST',
        data: ({orderItemId : itemId,...}),               
        success: function(data) {

           //construct a callback string if user is not logged in.
           var cllbck = 'addItem('+itemId +')';

           //Client check if login required
           //(#4 in flow diagram)
           if (verifyAuthentication(data,cllbck)){
               // in here => access to protected resource was ok
               // show message to user, "item has been added..."
           }
      });
    }

Steps #2, #3 - is a regular Spring Security configuration. Plenty of resources out there.
Step #4 - Client checks if login is required:
function verifyAuthentication(data, cllBackString){
   //naive check - I put a string in the login form, so I check for existance
   if (isNaN(data) && (data.indexOf("login_hidden_for_ajax")!= -1)){
      //if got here then data is a loginform => login required
      
      //set callback in ajax login form hidden input		
      $("#my_callback").val(cllBackString);	
 
      //show ajax login
      //Get the window height and width
      var winH = $(window).height();
      var winW = $(window).width();
              
      //Set the popup window to center
      $("#ajaxLogin").css('top',  winH/2-$("#ajaxLogin").height()/2);
      $("#ajaxLogin").css('left', winW/2-$("#ajaxLogin").width()/2);
      $("#ajaxLogin").fadeIn(2000); 
      return false;
      }	
    // data is not a login form => return true to continue with function processing
    return true;	
}
Step #5, #7 - the Ajax login FORM utilizes the following Ajax login:

function ajaxLogin(form, suffix){
	
	var my_callback = form.my_callback.value; // The original function which accessed the protected resource
	var user_pass = form.j_ajax_password.value;
	var user_name = form.j_ajax_username.value; 

//Ajax login - we send credentials to j_spring_security_check (as in form based login
	$.ajax({
          url: "/myContextURL/j_spring_security_check",    
          data: { j_username: user_name , j_password: user_pass }, 
          type: "POST",
          beforeSend: function (xhr) {
             xhr.setRequestHeader("X-Ajax-call", "true");
          },
          success: function(result) {    	
          //if login is success, hide the login modal and
          //re-execute the function which called the protected resource
          //(#7 in the diagram flow)
    	  if (result == "ok") {
 
            $("#ajax_login_error_"+ suffix).html("");            
    	    $('#ajaxLogin').hide();
    	    if (my_callback!=null && my_callback!='undefined' && my_callback!=''){
    		eval(my_callback.replace(/_/g,'"'));
    	    }
    	    
            return true;
          }else {       	
        	
        	$("#ajax_login_error_"+ suffix).html('<span  class="alert display_b clear_b centeralign">Bad user/password</span>') ;
        	return false;        	
        }
    },
    error: function(XMLHttpRequest, textStatus, errorThrown){
    	$("#ajax_login_error_"+ suffix).html("Bad user/password") ;
    	return false; 
    }
});
}

We need to set Spring to support Ajax login (#6):
Set Spring Security xml configuration:


   
    
        
        
		
		
        	
        
                    
        

    
	
	...

Define a handler for login success:
@Component("ajaxAuthenticationSuccessHandler")
public class AjaxAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {	
	   
    public AjaxAuthenticationSuccessHandler() {    
    }

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException {	
		
		HttpSession session = request.getSession();		
		DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) session.getAttribute(WebAttributes.SAVED_REQUEST);
		//check if login is originated from ajax call
		if ("true".equals(request.getHeader("X-Ajax-call"))) {
	        try {
	        	response.getWriter().print("ok");//return "ok" string
	        	response.getWriter().flush();
		} catch (IOException e) {				
		   //handle exception...
		}
	    } else {	    	
	    	setAlwaysUseDefaultTargetUrl(false);		
		...
	    }
	}
}
Define a handler for login failure - the same as success, but the string is "not-ok".

I know some of the code here is not the best practice so I would like to hear what you think.
Please post me if you can see a way to improve the process or make it more generic.

Acknowledgment -
Diagram was done via gliffy - online diagram tool

5 comments:

  1. Interesting post. I have come up with a similar solution, but quite frankly don't like it very much. I have been looking for a way for Spring Security to return a status other than 302->redirect to login page and then be able to validate against the status code as opposed to checking the return page for a special string. However, I have been unable to figure out how to do this.

    Is this still the solution that you are using, or have you found something different since this posting?

    ReplyDelete
    Replies
    1. Since it works well I didn't seek for other solution.. Will let you know if I'll find any :-)

      Delete
  2. it is handling success only . What about ajaxAuthenticationFailureHandler!!!
    Thanks in advance for above information

    ReplyDelete
    Replies
    1. Failure is very similar to the success handler, except it prints our "error" string.
      As you can see - the ajaxLogin JS method will handle only "ok" string as success.

      Delete
  3. Does this code correctly handle validation failures? E.g. if the password doesn't conform to a certain pattern you want more than not-ok, you want to return a bindingresult errors object with details on the validation issue.

    ReplyDelete