/* * Copyright 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.user.server.rpc; import com.google.gwt.user.client.rpc.RpcTokenException; import com.google.gwt.user.client.rpc.XsrfToken; import com.google.gwt.user.client.rpc.XsrfTokenService; import com.google.gwt.user.server.Util; import com.google.gwt.util.tools.shared.Md5Utils; import com.google.gwt.util.tools.shared.StringUtils; import javax.servlet.http.Cookie; /** * EXPERIMENTAL and subject to change. Do not use this in production code. * <p> * * </p> * RPC service to generate XSRF tokens. * <p> * Sample use of {@link XsrfTokenService}: * * <ol> * <li> Add {@link XsrfTokenServiceServlet} to {@code web.xml}: * * <pre> * <servlet> * <servlet-name>xsrf</servlet-name> * <servlet-class> * com.google.gwt.user.server.rpc.XsrfTokenServiceServlet * </servlet-class> * </servlet> * <servlet-mapping> * <servlet-name>xsrf</servlet-name> * <url-pattern>/gwt/xsrf</url-pattern> * </servlet-mapping> * </pre> * * <li> Specify session cookie name that is used for authentication. MD5 hash of * the session cookie's value will be used as an XSRF token: * * <pre> * <context-param> * <param-name>gwt.xsrf.session_cookie_name</param-name> * <param-value>JSESSIONID</param-value> * </context-param> * </pre> * * <li> To enforce XSRF token validation on each method call either mark RPC * interface as XSRF protected using {@link XsrfProtect} annotation or extend * {@link com.google.gwt.user.client.rpc.XsrfProtectedService} instead of * RemoteService. Use {@link NoXsrfProtect} to mark methods as not requiring * XSRF protection: * * <pre class="code"> * public interface MyRpcService extends XsrfProtectedService { * public void doStuff(); * } * </pre> * * <li> Ensure that RPC's servlet implementation extends {@link * XsrfProtectedServiceServlet} instead of {@link RemoteServiceServlet}: * * <pre class="code"> * public class MyRpcServiceServlet extends XsrfProtectedServiceServlet * implements MyRpcService { * * public void doStuff() { * // ... * } * } * </pre> * * <li> Obtain {@link XsrfToken} and set it on the RPC end point: * * <pre class="code"> * XsrfTokenServiceAsync xsrf = (XsrfTokenServiceAsync)GWT.create(XsrfTokenService.class); * * ((ServiceDefTarget)xsrf).setServiceEntryPoint(GWT.getModuleBaseURL() + "xsrf"); * * xsrf.getNewXsrfToken(new AsyncCallback<XsrfToken>() { * public void onSuccess(XsrfToken result) { * MyRpcServiceAsync rpc = (MyRpcServiceAsync)GWT.create(MyRpcService.class); * ((HasRpcToken) rpc).setRpcToken(result); * // make XSRF protected RPC call * rpc.doStuff(new AsyncCallback<Void>() { * // ... * }); * * } * * public void onFailure(Throwable caught) { * try { * throw caught; * } catch (RpcTokenException e) { * // Can be thrown for several reasons: * // - duplicate session cookie, which may be a sign of a cookie * // overwrite attack * // - XSRF token cannot be generated because session cookie isn't * // present * } catch (Throwable e) { * // unexpected * } * }); * </pre> * </ol> * </p> * * @see XsrfProtectedServiceServlet * @see XsrfProtect * @see NoXsrfProtect */ public class XsrfTokenServiceServlet extends RemoteServiceServlet implements XsrfTokenService { /** * Session cookie name initialization parameter. */ public static final String COOKIE_NAME_PARAM = "gwt.xsrf.session_cookie_name"; static final String COOKIE_NAME_NOT_SET_ERROR_MSG = "Session cookie name not set! Use '" + COOKIE_NAME_PARAM + "' context-param to specify session cookie name"; /** * Session cookie name. Cookie's value is used to generate XSRF cookie. */ private String sessionCookieName = null; /** * Default constructor. */ public XsrfTokenServiceServlet() { this(null); } /** * Alternative constructor that accepts session cookie name instead of getting * it from {@link javax.servlet.ServletConfig} or {@link * javax.servlet.ServletContext}. */ public XsrfTokenServiceServlet(String sessionCookieName) { this.sessionCookieName = sessionCookieName; } /** * Generates and returns new XSRF token. */ public XsrfToken getNewXsrfToken() { return new XsrfToken(generateTokenValue()); } /** * Servlet initialization. */ @Override public void init() { // do not overwrite values set via constructor if (sessionCookieName == null) { sessionCookieName = getInitParameterValue(COOKIE_NAME_PARAM); } if (sessionCookieName == null) { throw new IllegalStateException(COOKIE_NAME_NOT_SET_ERROR_MSG); } } /** * Generates new XSRF token. * * @return session cookie MD5 hash. */ private String generateTokenValue() { if (sessionCookieName == null) { throw new IllegalStateException(COOKIE_NAME_NOT_SET_ERROR_MSG); } // generate XSRF cookie using session cookie Cookie sessionCookie = Util.getCookie(getThreadLocalRequest(), sessionCookieName, false); if (sessionCookie == null || sessionCookie.getValue() == null || sessionCookie.getValue().length() == 0) { throw new RpcTokenException("Session cookie is not set or empty! " + "Unable to generate XSRF cookie"); } byte[] cookieBytes = sessionCookie.getValue().getBytes(); return StringUtils.toHexString(Md5Utils.getMd5Digest(cookieBytes)); } /** * Retrieves and returns specified initialization parameter first from * {@link ServletConfig} followed by {@link ServletContext}, if former returns * {@code null}. */ private String getInitParameterValue(String name) { String paramValue = null; paramValue = getServletConfig().getInitParameter(name); if (paramValue == null) { paramValue = getServletContext().getInitParameter(name); } return paramValue; } }