All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cedarsolutions.wiring.gwt.rpc.SecuredServiceExporter Maven / Gradle / Ivy

There is a newer version: 5.8.4
Show newest version
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *              C E D A R
 *          S O L U T I O N S       "Software done right."
 *           S O F T W A R E
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Copyright (c) 2013 Kenneth J. Pronovici.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Apache License, Version 2.0.
 * See LICENSE for more information about the licensing terms.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Author   : Kenneth J. Pronovici 
 * Language : Java 6
 * Project  : Common Java Functionality
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
package com.cedarsolutions.wiring.gwt.rpc;

import java.lang.reflect.Method;

import org.apache.log4j.Logger;
import org.gwtwidgets.server.spring.GWTRPCServiceExporter;

import com.cedarsolutions.exception.ServiceException;
import com.cedarsolutions.server.service.IXsrfTokenService;
import com.cedarsolutions.util.LoggingUtils;
import com.google.gwt.user.client.rpc.RpcToken;
import com.google.gwt.user.client.rpc.RpcTokenException;
import com.google.gwt.user.server.Util;
import com.google.gwt.user.server.rpc.NoXsrfProtect;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.XsrfProtect;

/**
 * Customized GWT service exporter.
 *
 * 

Error Handing

* *

* GWT doesn't like it when service calls throw exceptions that aren't declared * in the method signature. If this happens, you get a 500 (server error) rather * than something more useful. In particular, it causes problems when Spring * security is involved, since we really want to see a 403 (forbidden) so we can * handle it explicitly. *

* *

* This class takes all unexpected failures and converts them to * ServiceException. Make sure that all of your service methods declare * throws ServiceException. If you do this, you'll get a legible * exception that you can catch in your service handler: *

* *
 *     public void onFailure(Throwable caught) {
 *      try {
 *          throw caught;
 *      } catch (StatusCodeException e) {
 *          HttpStatusCode statusCode = HttpStatusCode.convert(e.getStatusCode());
 *          switch(statusCode) {
 *          case FORBIDDEN:
 *              // do what's needed for a "not authorized" error
 *              break;
 *          default:
 *              // do what's needed for a generic service error
 *              break;
 *          }
 *      } catch (Throwable e) {
 *          // do what's needed for a generic service error
 *      }
 *  }
 * 
* *

CSRF/XSRF Protection

* *

* If enabled via the enableXsrfProtection flag, this code attempts to * implement protection against CSRF/XSRF attacks (cross site request forgery). * Two different approaches are taken. *

* *

* The first approach is a very limited check based on the permutation strong * name field that GWT automatically includes in the HTTP request header. If * the permutation strong name is empty, then the request is assumed to be a * CRSF/XSRF attack. The code below uses the standard check that's provided * with GWT, but overrides the standard GWT method to throw an RpcTokenException * rather than a SecurityException. This makes it possible to handle the error * more explicitly on the client side. *

* *

* The second approach is more sophisticated. Any "protected" RPC call must be * preceded by a call to to retrieve a cryptographic token. This token is * included in the RPC call and is validated on the server side. By validating * this token, we can block requests from "evil" sources, since any attacker * will theoretically not have the means to generate a valid token. (They can * get the browser to make the request on their behalf — that's the * forgery part — but they can't read the session cookie's value in order * to generate a valid token.) *

* *

* To protect an RPC call, you annotate the service interface with * \@XsrfProtect. However, remember that the mechanism depends on a session * being available. Practically speaking, this means that you can only use * \@XsrfProtect on RPCs that are also marked with \@Secured. That shouldn't * present a problem, since in the context of a CSRF/XSRF attack, you're only * worried about services that can actually accomplish something useful, and * you would normally secure those services anyway. *

* *

* The implementation below has been backported from the XsrfProtectedServiceServlet * in GWT 2.4.0. There's no opportunity to inherit from that class while still * using GWT-SL. However, since the GWTRPCServiceExporter and XsrfProtectedServiceServlet * both extend the same class, the code can be pulled out of XsrfProtectedServiceServlet * and implemented here. I followed the basic outline from XsrfProtectedServiceServlet, * and then had to make several adjustments so that RpcTokenException would get thrown * back to the RPC caller in a legible way. *

* * @see Add authentication to your GWT application * @see CSRF Prevention? * @see GWT RPC XSRF protection * * @author Kenneth J. Pronovici */ public class SecuredServiceExporter extends GWTRPCServiceExporter { /** Logger instance. */ private static Logger LOGGER = LoggingUtils.getLogger(SecuredServiceExporter.class); /** Serialization version number. */ private static final long serialVersionUID = 1L; /** Whether to enable CSRF/XSRF protection. */ private boolean enableXsrfProtection; /** CSRF/XSRF token service. */ private IXsrfTokenService xsrfTokenService; /** * Create a service exporter, optionally enabling CSRF/XSRF protection. * @param enableXsrfProtection Whether to enable CSRF/XSRF protection * @param xsrfTokenService CSRF/XSRF token service to use */ public SecuredServiceExporter(boolean enableXsrfProtection, IXsrfTokenService xsrfTokenService) { super(); this.setEnableXsrfProtection(enableXsrfProtection); this.setShouldCheckPermutationStrongName(enableXsrfProtection); this.setXsrfTokenService(xsrfTokenService); } /** Wrap any unexpected exception from an RPC method call in a ServiceException. */ @Override protected void doUnexpectedFailure(Throwable exception) { throw new ServiceException("Unexpected failure in service call: " + exception.getMessage(), exception); } /** Handle exceptions thrown by the CSRF/XSRF token checking mechanism. */ @Override protected String handleExporterProcessingException(Exception exception) { if (exception instanceof RpcTokenException) { try { return this.encodeResponseForFailure(exception); } catch (Exception e) { return super.handleExporterProcessingException(exception); } } else { return super.handleExporterProcessingException(exception); } } /** Apply CSRF/XSRF validation to the request after it has been deserialized. */ @Override protected void onAfterRequestDeserialized(RPCRequest request) { if (shouldValidateXsrfToken(request.getMethod())) { validateXsrfToken(request.getRpcToken()); } } /** * Indicates whether the CSRF/XSRF token should be validated for a particular method. * @param method Method being invoked * @return True if CSRF/XSRF token should be verified, false otherwise. */ protected boolean shouldValidateXsrfToken(Method method) { if (!this.enableXsrfProtection) { LOGGER.debug("CSRF/XSRF protection is disabled on the server side."); return false; } else { boolean enabled = Util.isMethodXsrfProtected(method, XsrfProtect.class, NoXsrfProtect.class, RpcToken.class); LOGGER.debug("CSRF/XSRF protection is " + (enabled ? "enabled" : "disabled") + " for method " + getMethodName(method)); return enabled; } } /** Override the standard permutation strong name check to throw an RpcTokenException. */ @Override protected void checkPermutationStrongName() throws SecurityException { try { super.checkPermutationStrongName(); } catch (SecurityException e) { LOGGER.error("Possible CSRF/XSRF attack: permutation strong name was empty"); throw new RpcTokenException("Request blocked: permutation strong name was invalid"); } } /** * Perform CSRF/XSRF token validation. * @param token Token included with an RPC request * @throws RpcTokenException If token verification failed. */ protected void validateXsrfToken(RpcToken token) throws RpcTokenException { this.xsrfTokenService.validateXsrfToken(token); } /** Get a legible method name based on a method argument. */ private static String getMethodName(Method method) { return method.getDeclaringClass().getSimpleName() + "." + method.getName(); } public boolean getShouldCheckPermutationStrongName() { return this.shouldCheckPermutationStrongName; } public boolean getEnableXsrfProtection() { return this.enableXsrfProtection; } public void setEnableXsrfProtection(boolean enableXsrfProtection) { this.enableXsrfProtection = enableXsrfProtection; } public IXsrfTokenService getXsrfTokenService() { return this.xsrfTokenService; } public void setXsrfTokenService(IXsrfTokenService xsrfTokenService) { this.xsrfTokenService = xsrfTokenService; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy