Saturday, January 8, 2011

Spring and GWT: Security via Spring Security

In this tutorial we will secure our existing GWT application through Spring Security. We will create a transparent layer for managing our application's security. We will be using the application we built in the following tutorial: Spring and GWT Integration using Maven and GWTHandler (Part 1). Please read that first, so that you have a basic idea of the project's structure. Also, please read Spring Security 3 - MVC: Using a Simple User-Service Tutorial for a basic introduction in setting-up Spring Security.

What is Spring Security?
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications

Spring Security is one of the most mature and widely used Spring projects. Founded in 2003 and actively maintained by SpringSource since, today it is used to secure numerous demanding environments including government agencies, military applications and central banks.

Source: http://static.springsource.org/spring-security/site/index.html

What is GWT?
Google Web Toolkit (GWT) is a development toolkit for building and optimizing complex browser-based applications. GWT is used by many products at Google, including Google AdWords and Orkut. It's open source, completely free, and used by thousands of developers around the world.

Source: http://code.google.com/webtoolkit/

We start by editing the web.xml file to enable the Spring Security filters (We will not discuss again what we've covered in the previous tutorials).

web.xml





 
 
  contextConfigLocation    /WEB-INF/spring-security.xml
  /WEB-INF/applicationContext.xml
   
 
 
   
         springSecurityFilterChain
         org.springframework.web.filter.DelegatingFilterProxy
 
 
 
 
         springSecurityFilterChain
         /*
 

 
 
  org.springframework.web.context.ContextLoaderListener
 

 
 
  gwt
  org.springframework.web.servlet.DispatcherServlet
  1
 
 
 
 
  spring
  org.springframework.web.servlet.DispatcherServlet
  1
 
 
 
 
  gwt
  gwtmodule/rpc/*
 
 
 
 
  spring
  krams/*
 

We have declared a reference to a spring-security.xml configuration. This contains the Spring Security settings for our application.

spring-security.xml


 
 
 
 
  
  
  
  
  
  
  
  
  
  
  
     
  
  
   
  
 
 
 
 
 
         
           
         
 
 
 
 

  
  
  
     
     
   
 
   
 
 
   
 
 
  
 
  
The defining configuration here are the custom beans we've created. Here's the reason: In a normal Spring MVC application with Spring Security enabled regardless whether the user is authorized or not, he will be redirected to a corresponding View, i.e a JSP page. However, GWT is an AJAX application that operates in the RPC level. If you try redirecting to a page, it will not work. The solution is to provide a custom HttpServletResponse response to tell GWT that the user is authorized or not. These responses are HTTP status codes:
200 - Authenticated and authorized
401 - Not authenticated and not authorized
401 - (Or) authenticated but not authorized

The developer's job is to interpret what to do next for these set of responses. Here are the custom beans.

CustomAuthenticationEntryPoint
/**
 * 
 */
package org.krams.tutorial.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

 protected static Logger logger = Logger.getLogger("security");
 
 public void commence(HttpServletRequest request, HttpServletResponse response,
   AuthenticationException exception) throws IOException, ServletException {

   logger.debug("Authentication required");
   
    HttpServletResponse httpResponse = (HttpServletResponse) response;
         httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication required");
 }

}
CustomAuthenticationSuccessHandler
package org.krams.tutorial.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

public class CustomAuthenticationSuccessHandler implements
  AuthenticationSuccessHandler {

 protected static Logger logger = Logger.getLogger("security");

 public void onAuthenticationSuccess(HttpServletRequest request,
   HttpServletResponse response, Authentication authentication) throws IOException,
   ServletException {
  
   logger.debug("Authentication success");
   
     HttpServletResponse httpResponse = (HttpServletResponse) response;
         httpResponse.sendError(HttpServletResponse.SC_OK, "Authentication success");
 }

}
CustomAuthenticationFailureHandler
package org.krams.tutorial.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

public class CustomAuthenticationFailureHandler implements
  AuthenticationFailureHandler {

 protected static Logger logger = Logger.getLogger("security");
 
 public void onAuthenticationFailure(HttpServletRequest request,
   HttpServletResponse response, AuthenticationException exception)
   throws IOException, ServletException {
  
   logger.debug("Authentication failure");
   
   HttpServletResponse httpResponse = (HttpServletResponse) response;
   httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failure");  
 }

}
Let's run our application and see what will happen. Again, we use the Maven gwt:run goal to execute the application.


After sending our name, we get a server error message. How do we know that this is caused by Spring Security and not by a missing RPC service? We check the logs!

Here's the Jetty log:
00:00:18.667 [WARN] 401 - POST /gwtmodule/rpc/greet (127.0.0.1) 1433 bytes

Here's the log4j log:
[DEBUG] [btpool0-0 04:34:46] (CustomAuthenticationEntryPoint.java:commence:23) Authentication required

Clearly it's because we're not authorized. At this point, we know our services are secured! However, the error message is misleading. We need to provide a friendly message.

Go the client package of our project. Open the gwtmodule.java, and replace the contents of the sendNametoServer() method with the following:

gwtmodule.java
  /**
       * Send the name from the nameField to the server and wait for a response.
       */
      private void sendNameToServer() {
        // First, we validate the input.
        errorLabel.setText("");
        final String textToServer = nameField.getText();
        if (!FieldVerifier.isValidName(textToServer)) {
          errorLabel.setText("Please enter at least four characters");
          return;
        }

        // Then, we send the input to the server.
        sendButton.setEnabled(false);
        textToServerLabel.setText(textToServer);
        serverResponseLabel.setText("");
        
        // We first check if user is authorized by checking a flag URL
        
        // Create a new request
        RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, "/krams/main/admin");
  rb.setCallback(new RequestCallback() {
   
      public void onError(Request request, Throwable exception) {
       // Error in sending request
      }
      
      public void onResponseReceived(Request request, Response response) {
          if (response.getStatusCode() == 200) {
           
           // Send the input to the server.
           greetingService.greetServer(textToServer, new AsyncCallback() {
                  public void onFailure(Throwable caught) {
                    // Show the RPC error message to the user
                    dialogBox.setText("Remote Procedure Call - Failure");
                    serverResponseLabel.addStyleName("serverResponseLabelError");
                    //serverResponseLabel.setHTML(SERVER_ERROR);
                    serverResponseLabel.setHTML(caught.getMessage());
                    dialogBox.center();
                    closeButton.setFocus(true);
                  }

                  public void onSuccess(String result) {
                    dialogBox.setText("Remote Procedure Call");
                    serverResponseLabel.removeStyleName("serverResponseLabelError");
                    serverResponseLabel.setHTML(result);
                    dialogBox.center();
                    closeButton.setFocus(true);
                  }
                });
     
          } else {
             // User is not authorized!
             // Show the RPC error message to the user
                   dialogBox.setText("Remote Procedure Call - Failure");
                   serverResponseLabel.addStyleName("serverResponseLabelError");
                   serverResponseLabel.setHTML("You are not authorized!");
                   dialogBox.center();
                   closeButton.setFocus(true);
          }
      }
  });
  
  try {
   rb.send();
  } catch (RequestException re) {
   //Log.error("Exception in sending RequestBuilder: " + re.toString());
  }
  
      }
    }
What we did here is wrap our RPC service request inside GWT's RequestBuilder. Also, we called a custom URL that points to a JSP page to verify the authentication of the user. This JSP page as declared in the spring-security.xml has the following access level:

<security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/>
This URL is only accessible if the user is authenticated and has admin rights. If we can access this then the current user has the rights.

What is RequestBuilder?
Provides the client-side classes and interfaces for making HTTP requests and processing the associated responses.

Most applications will be interested in the Request, RequestBuilder, RequestCallback and Response classes.

Source: http://www.gwtapps.com/doc/html/com.google.gwt.http.client.html

For more info about RequestBuilder, please visit this link.

Notice in the code, we only execute the GreetingService's greetServer() method when the status code is equal to 200
if (response.getStatusCode() == 200) {
  ...
}
If the status code is different, we send a customized error message:
...
else {
 // User is not authorized!
 // Show the RPC error message to the user
 dialogBox.setText("Remote Procedure Call - Failure");
 serverResponseLabel.addStyleName("serverResponseLabelError");
 serverResponseLabel.setHTML("You are not authorized!");
 dialogBox.center();
 closeButton.setFocus(true);
}
Let's run our application again and verify the results.


We now have a friendlier error message. That's great. But how do we log in? We can't.

We have to create a custom login screen either via a JSP page (if you have Spring MVC enabled) or via GWT (this means you'll need to know how to use widgets, similar with the greet server interface). To make our lives easier, we'll use a JSP page because in the first place Spring MVC is already enabled in the web.xml, spring-servlet.xml, and applicationContext.xml

This means we need to add corresponding controllers and JSP pages. If you to refresh your memory on Spring MVC and integration with Spring Security, please visit the following tutorial: Spring Security 3 - MVC Integration Tutorial.

Here are our controllers:

LoginLogoutController
/**
 * 
 */
package org.krams.tutorial.controller;


import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * Handles and retrieves the login or denied page depending on the URI template
 */
@Controller
@RequestMapping("/auth")
public class LoginLogoutController {
        
 protected static Logger logger = Logger.getLogger("controller");

 /**
  * Handles and retrieves the login JSP page
  * 
  * @return the name of the JSP page
  */
 @RequestMapping(value = "/login", method = RequestMethod.GET)
 public String getLoginPage(@RequestParam(value="error", required=false) boolean error, 
   ModelMap model) {
  logger.debug("Received request to show login page");

  // Add an error message to the model if login is unsuccessful
  // The 'error' parameter is set to true based on the when the authentication has failed. 
  // We declared this under the authentication-failure-url attribute inside the spring-security.xml
  // However this doesn't seem to work when GWT is enabled
  if (error == true) {
   // Assign an error message
   model.put("error", "You have entered an invalid username or password!");
  } else {
   model.put("error", "");
  }
  
  // This will resolve to /WEB-INF/jsp/loginpage.jsp
  return "loginpage";
 }
 
 /**
  * Handles and retrieves the denied JSP page. This is shown whenever a regular user
  * tries to access an admin only page.
  * 
  * @return the name of the JSP page
  */
 @RequestMapping(value = "/denied", method = RequestMethod.GET)
  public String getDeniedPage() {
  logger.debug("Received request to show denied page");
  
  // This will resolve to /WEB-INF/jsp/deniedpage.jsp
  return "deniedpage";
 }
}
MainController
package org.krams.tutorial.controller;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Handles and retrieves the common or admin page depending on the URI template.
 * A user must be log-in first he can access these pages.  Only the admin can see
 * the adminpage, however.
 */
@Controller
@RequestMapping("/main")
public class MainController {

 protected static Logger logger = Logger.getLogger("controller");
 
 /**
  * Handles and retrieves the common JSP page that everyone can see
  * 
  * @return the name of the JSP page
  */
    @RequestMapping(value = "/common", method = RequestMethod.GET)
    public String getCommonPage() {
     logger.debug("Received request to show common page");
    
     // Do your work here. Whatever you like
     // i.e call a custom service to do your business
     // Prepare a model to be used by the JSP page
     
     // This will resolve to /WEB-INF/jsp/commonpage.jsp
     return "commonpage";
 }
    
    /**
     * Handles and retrieves the admin JSP page that only admins can see
     * 
     * @return the name of the JSP page
     */
    @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public String getAdminPage() {
     logger.debug("Received request to show admin page");
    
     // Do your work here. Whatever you like
     // i.e call a custom service to do your business
     // Prepare a model to be used by the JSP page
     
     // This will resolve to /WEB-INF/jsp/adminpage.jsp
     return "adminpage";
 }
}
Here are the JSP pages.

loginpage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Login</h1>

<div id="login-error">${error}</div>

<form action="../../j_spring_security_check" method="post" >

<p>
 <label for="j_username">Username</label>
 <input id="j_username" name="j_username" type="text" />
</p>

<p>
 <label for="j_password">Password</label>
 <input id="j_password" name="j_password" type="password" />
</p>

<input  type="submit" value="Login"/>        
 
</form>

</body>
</html>
deniedpage.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Access Denied!</h1>
<p>Only admins can see this page!</p>
</body>
</html>
adminpage.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Admin Page</h1>
<p>Only admins have access to this page.</p>
<p>Curabitur quis libero elit, dapibus iaculis nisl. Nullam quis velit eget odio 
adipiscing tristique non sed ligula. In auctor diam eget nisl condimentum laoreet..</p>
</body>
</html>
commonpage.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Common Page</h1>
<p>Everyone has access to this page.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ac velit et ante 
laoreet eleifend. Donec vitae augue nec sem condimentum varius.</p>
</body>
</html>

To login using the JSP page, open a new browser or a new tab. Then use the following URL:
http://127.0.0.1:8888/krams/auth/login
Here's the login page.

Notice there's an error ${error}. In a normal Spring MVC application, this should be hidden and only populated via a model object. I decided to include this error here anyway to remind my readers that integrating GWT and Spring have some disadvantages as well.

Try logging-in. Use john for the username and admin for the password.

You should see the following message:
HTTP ERROR: 200

Authentication success
RequestURI=/j_spring_security_check

Powered by Jetty://
We receive an HTTP error because the URL that we'd been redirected http://127.0.0.1:8888/j_spring_security_check doesn't exist for viewing. But notice we're authenticated.

Now, try sending a message again. Go the GWT application, and send a message. Here's what we should get:

That's it. We're created a simple GWT application that's secured by Spring Security. We've explored ways of integrating both technologies, including Spring MVC. We've also discussed how we can use RequestBuilder for requesting standard resources.

As a side note, you can create a GWT login-page that delegates the authentication via RequestBuilder. For example:
RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, "/j_spring_security_check");

rb.setHeader("Content-Type", "application/x-www-form-urlencoded");

rb.setRequestData("j_username=" + URL.encode(myusername + "&j_password=" + URL.encode("mypassword")));


Notice how it's similar with a standard JSP login page:
<form action="../../j_spring_security_check" method="post" >

<p>
 <label for="j_username">Username</label>
 <input id="j_username" name="j_username" type="text" />
</p>

<p>
 <label for="j_password">Password</label>
 <input id="j_password" name="j_password" type="password" />
</p>

<input  type="submit" value="Login"/>        
 
</form>

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-gwt/

You can download the project as a Maven build. Look for the spring-gwt-security.zip in the Download sections.

You can run the project directly using the Maven GWT plugin.
mvn gwt:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.

17 comments:

  1. Used this example working fine, only one issue DI is not working in my case:
    I have declared this :



    in gwt-servlet.xml

    ResellerServiceImpl class getting loaded at start up, but all properties eligible for DI are null want some call comes in, i tried @Resource and @Autowire, but the same thing is working in non-gwt projects, please suggest something

    ReplyDelete
  2. solved : had to add component-scan tag in gwt-servlet.xml

    thanks
    Aashu jaidka

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Hi,

    I have added Spring security(with CAS and Ldap) in my gwt application.But after successful integration. I am unable to view my gwt html page its only shows a blank page after ticket Validation.

    My web.xml is as follows :

    Validation Filter
    org.springframework.web.filter.DelegatingFilterProxy

    targetBeanName
    casValidationFilter




    Validation Filter
    /*


    org.jasig.cas.client.session.SingleSignOutHttpSessionListener


    CAS Single Sign Out Filter
    org.jasig.cas.client.session.SingleSignOutFilter


    CAS Single Sign Out Filter
    /*


    CAS Authentication Filter
    org.springframework.web.filter.DelegatingFilterProxy

    targetBeanName
    authenticationFilter



    CAS Authentication Filter
    /*


    And my app-context.xml is :


















    Please check it why its coming a blank page.

    ReplyDelete
  5. And my app-context.xml is :


















    Please check it why its coming a blank page.

    ReplyDelete
  6. Isabel Marant and even the groom Jér? all of us Dreyfuss kkyyttssddfsafafda contain crafted pattern ones own get the job done, constructing an important cult fashion sections (the) and even up-and-coming accents sections.jimmy choo april

    ReplyDelete
  7. sdfsdfsdfdsfsdfsdrrww
    A lot of people explained that they can chosen these kind of skinny jeans in Elle Macpherson. That they feel that your Isabel Marant skinny jeans go well with Drew’s fashion adequately plus the tie up absorb dyes moves adequately using your ex bohemian fashion, nevertheless your ex various other equipment create your clothing way too occupied, along with these kind of skinny jeans are generally excellent automatically along with needs to be the concentration in the clothing! Probably these are appropriate, isabel marant cleane boots nevertheless every single gentleman features the hobbyhorse, My spouse and i even now like these kind of Isabel Marant skinny jeans in Drew Barrumore along with your ex set is ideal many people feel.

    ReplyDelete
  8. Hi, I have tried to login but login page is not working. I tried Your example and I don't know how can I login. I use Jetty server. Can You help me? Please feed back. Thanks.

    ReplyDelete
  9. wonderful article! was looking for this for some time. thank you you beutiful man!

    ReplyDelete
  10. Thanks for the great article, very useful. I think there is a serious flaw in the non-GWT access control though. If you try to access /admin as an anonymous user then it is correctly denied, but if you access /admin/ then access is granted without logging in.

    ReplyDelete
  11. Thank you for taken the time to write this. Really helped and was the most useful tutorial I found on this topic.

    ReplyDelete
  12. I have read your blog its very attractive and impressive. I like it your blog.

    Spring online training Spring online training Spring Hibernate online training Spring Hibernate online training Java online training

    spring training in chennai spring hibernate training in chennai

    ReplyDelete
  13. Considering to join additional affiliate programs?
    Visit my affiliate directory to see the ultimate list of affiliate networks.

    ReplyDelete
  14. Happy to found this blog. Good Post!. It was so good to read and useful to improve my knowledge as updated one, keep blogging. Hibernate Training in Electronic City
    Java Training in Electronic City

    ReplyDelete
  15. This comment has been removed by the author.

    ReplyDelete
  16. Awesome. You have clearly explained …Its very useful for me to know about new things. Keep on blogging.
    FullStack development training
    Java training
    Spring Boot and Micro services training

    ReplyDelete