blob: fa5343db98972c35022218833036dfff12348f9c [file] [log] [blame]
/*
* 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.Utility;
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>
* &lt;servlet&gt;
* &lt;servlet-name&gt;xsrf&lt;/servlet-name&gt;
* &lt;servlet-class&gt;
* com.google.gwt.user.server.rpc.XsrfTokenServiceServlet
* &lt;/servlet-class&gt;
* &lt;/servlet&gt;
* &lt;servlet-mapping&gt;
* &lt;servlet-name&gt;xsrf&lt;/servlet-name&gt;
* &lt;url-pattern&gt;/gwt/xsrf&lt;/url-pattern&gt;
* &lt;/servlet-mapping&gt;
* </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>
* &lt;context-param&gt;
* &lt;param-name&gt;gwt.xsrf.session_cookie_name&lt;/param-name&gt;
* &lt;param-value>JSESSIONID&lt;/param-value&gt;
* &lt;/context-param&gt;
* </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&lt;XsrfToken&gt;() {
* 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&lt;Void&gt;() {
* // ...
* });
*
* }
*
* 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 Utility.toHexString(Utility.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;
}
}