Wednesday, May 19, 2010

GWT and Spring Security

Update! - Based on the post below, and my other post regarding Spring Security and OpenID, I have added Open-ID support to the sample application below. For those interested, here's the write-up of changes.



I've spent quite some time digging into ways of integrating GWT and Spring Security. It all started by reading the following post in the GWT Forum - Best practices/ideas for GWT with Spring Security (or equivalent), and then checking out this blog - GWT and Spring Security. To make matters worse, I started reading Security for GWT Applications and specifically about the "Cross-Site Request Forging"-attacks.

Now, what could I do about it?

Well, starting by setting up my own project (Maven-based) with all updated dependencies (GWT 2.0.3 etc) and started reading the Spring Security Reference Documentation (puh!).

Instead of See Wah Cheng's approach of implementing a custom authentication service, I decided to rely on standard namespace configuration (with some extra configuration, of course). So, after adding the normal filter declaration into web.xml I came up with this configuration for my "applicationContext-security.xml":

<http auto-config="true" entry-point-ref="http401UnauthorizedEntryPoint"
  create-session="always">
  <form-login authentication-success-handler-ref="authenticationSuccessHandler"
   authentication-failure-handler-ref="authenticationFailureHandler" />
  <logout success-handler-ref="logoutSuccessHandler" />

  <custom-filter before="CONCURRENT_SESSION_FILTER" ref="XSRFAttackFilter" />
 </http>

 <beans:bean id="XSRFAttackFilter"
  class="com.myappenginecookbook.security.XSRFAttackFilter" />

 <!--
  Use this entry point to signal to the GWT-caller that the user needs
  to log in to access the resource
 -->
 <beans:bean id="http401UnauthorizedEntryPoint"
  class="com.myappenginecookbook.security.Http401UnauthorizedEntryPoint" />

 <beans:bean id="authenticationSuccessHandler"
  class="com.myappenginecookbook.security.GWTAuthenticationSuccessHandler" />
 <beans:bean id="authenticationFailureHandler"
  class="com.myappenginecookbook.security.GWTAuthenticationFailureHandler" />
 <beans:bean id="logoutSuccessHandler"
  class="com.myappenginecookbook.security.GWTLogoutSuccessHandler" />

Don't worry about the XSRFAttackFilter class for now (we'll get to it).

I'll start by explaining how the entry-point works, and work my way deeper into the code from there...

Basically, I'm relying on standard HTTP Status Codes to signal to the caller (GWT) what Spring Security is requesting, hence the entry-point will always return HttpServletResponse.SC_UNAUTHORIZED:

/**
     * Always returns a 401 error code to the client.
     */
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) throws IOException,
            ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Pre-authenticated entry point called. Rejecting access");
        }
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication required");
    }

The GWTAuthenticationSuccessHandler, GWTAuthenticationFailureHandler and GWTLogoutSuccessHandler works the same way, i.e. always returning HttpServletResponse.SC_OK, HttpServletResponse.SC_UNAUTHORIZED and HttpServletResponse.SC_OK respectively.

The response needs to be picked up in the onFailure()-method of the calling method and handled. I created an AutoErrorHandlingAsyncCallback that will show a login dialog whenever a user needs to login. It's capable of re-submitting the unauthorized command once the user is successfully logged in, but the interesting part is the login functionality itself:

@UiHandler("loginButton")
 void login(ClickEvent e) {
  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(email.getText() + "&j_password=" + URL.encode(password.getText())));
  
  rb.setCallback(new RequestCallback() {
      public void onError(Request request, Throwable exception) {
       showError();
          Log.error(exception.getMessage());
      }
      public void onResponseReceived(Request request, Response response) {
          if (response.getStatusCode() == 200) {
           // notify all interested components
           fireEvent(new LoginEvent(true));
           
           // issue the command that triggered the dialog
           if (cmd != null) {
            cmd.execute();
           }
           
           hide();
           
           Log.debug("[success (" + response.getStatusCode() + "," + response.getStatusText() + ")]");
          } else {
           showError();
           Log.error(response.getStatusCode() + "," + response.getStatusText());
          }
      }
  });
  
        try {
   rb.send();
  } catch (RequestException re) {
   re.printStackTrace();
  }
 }

As you can see, I'm posting to the standard form login page using the RequestBuilder, and the returning response will hold the status codes from the handlers described above.

Given that we came here due to trying to access a restricted resource, the login dialog will re-issue the same command. Also note that it will fire a custom event whenever a user is successfully authenticated, so that other widgets can take appropriate actions.

Now - back to the XSRFAttackFilter! Reading about how to protect your application from XSRF-attacks I stumbled over the following text: Perhaps the simplest solution is to simply add the cookie value to your URL as a GET parameter. The important thing is to get the cookie value up to the server, somehow..

I decided to add a way for the caller to submit the cookie value with any call to the server by modifying the ServiceEntryPoint of the Async-interface:

/**
     * Utility class to get the RPC Async interface from client-side code, with the improved
     * duplication of session cookie information, see {@link http://groups.google.com/group/Google-Web-Toolkit/web/security-for-gwt-applications}.
     * 
     * To be used in conjunction with the {@link XSRFAttackFilter}. 
     */
    public static final class SecureUtil  { 
     private static String entryPoint = null;
     
        public static final SecureServiceAsync getInstance() {
         SecureServiceAsync instance = SecureServiceAsync.Util.getInstance();
         ServiceDefTarget target = (ServiceDefTarget) instance;
         
         if (entryPoint == null) {
          entryPoint = target.getServiceEntryPoint();
         }
                
            target.setServiceEntryPoint(entryPoint + "?JSESSIONID="+Cookies.getCookie("JSESSIONID"));
                
                return instance;
            }
            

        private SecureUtil()
        {
            // Utility class should not be instantiated
        }
    } 

So, instead of calling SecureServiceAsync.Util.getInstance() you'd call SecureService.SecureUtil.getInstance(). This extra piece of information is picked up by the custom XSRFAttackFilter and will be matched against the session id on the server:

@Override
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
     throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        
        String cookieId = request.getParameter("JSESSIONID");
        String sessionId = request.getSession().getId();
        
        if (log.isLoggable(Level.FINE)) {
         log.fine("cookieId=" + cookieId + " / sessionId=" + sessionId);
        }
        
        // if the user is authenticated, the cookie session id and the id sent as a request param must match
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication != null && !sessionId.equals(cookieId)) {
         SecurityContextHolder.clearContext();         
         throw new SessionAuthenticationException("Invalid session - you have been logged out!");
        }
        
        chain.doFilter(request, response);
 }

That's it! Hopefully this will give you some more ideas of how GWT and Spring Security can be used together!

For those of you that want a running example, here's the full project source.

Thursday, May 13, 2010

Spring's mailSender with Google App Engine

Ever wanted to use Spring's mailSender in a GAE hosted application? This is how it's done!

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
  <!-- Specific Google protocol used with Google App Engine -->
  <property name="protocol" value="gm" />
 </bean>

Wednesday, May 12, 2010

Introducing Technowobble!

Technowobble is my place to post interesting stuff related to technology in general.

Right now I'm spending time looking into Spring integration with Google Web Toolkit (GWT) in combination with Google App Engine (GAE) and fibbling with my brand new Mac Mini - which is hooked up to my Samsung flatscreen and only accessed through my Iphone (no keyboard, no mouse!).

Another thing catching my tech-attention right now is Spring Roo - I wonder what will be?

Happy reading!