安全 RPC XSRF

跨站点请求伪造 (XSRF 或 CSRF) 是一种网络攻击,攻击者可以在不知情的情况下代表经过身份验证的用户执行操作。通常,它涉及制作一个恶意 HTML 页面,一旦受害者访问该页面,就会导致受害者的浏览器向第三方域发出攻击者控制的请求。如果受害者已向第三方域进行身份验证,则请求将与该域的浏览器 Cookie 一起发送,并且可能会在不知情的情况下代表受害者触发不希望的操作 - 例如,删除或修改博客或添加邮件转发规则。

本文档解释了开发人员如何使用 GWT 2.3 中引入的 GWT 内置 XSRF 防护来保护 GWT RPC 免受 XSRF 攻击。

  1. 概述
  2. 服务器端更改
  3. 客户端更改

概述

RPC XSRF 防护是使用 RpcToken 功能构建的,该功能允许开发人员使用 HasRpcToken 接口在 RPC 端点上设置令牌,并在通过该端点进行的每个 RPC 调用中包含该令牌。

默认 XSRF 防护实现从会话身份验证 Cookie 中派生 XSRF 令牌,方法是生成会话 Cookie 值的 MD5 哈希,并将生成的哈希用作 XSRF 令牌。这种无状态 XSRF 防护实现依赖于以下事实:攻击者无法访问会话 Cookie,因此无法生成有效的 XSRF 令牌。

服务器端更改

配置 XsrfTokenServiceServlet

客户端代码将通过调用 XsrfTokenService.getNewXsrfToken() 服务器端实现(在 web.xml 中配置)来获取 XSRF 令牌。

<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>

由于 XSRF 令牌与身份验证会话 Cookie 相关联,因此必须将该 Cookie 的名称传递给 XsrfTokenServiceServlet 以及所有受 XSRF 保护的 RPC 服务 servlet。这通过 web.xml 中的上下文参数完成。

<context-param>
  <param-name>gwt.xsrf.session_cookie_name</param-name>
  <param-value>JSESSIONID</param-value>
</context-param>

注意:Servlet 初始化参数(<init-param>)也可以用于将会话 Cookie 的名称分别传递给每个 servlet。

使 RPC servlet 受 XSRF 保护

所有 RPC 服务的服务器端实现必须扩展 XsrfProtectedServiceServlet

package com.example.foo.server;

import com.google.gwt.user.server.rpc.XsrfProtectedServiceServlet; 

import com.example.client.MyService;

public class MyServiceImpl extends XsrfProtectedServiceServlet implements
    MyService {

  public String myMethod(String s) {
    // Do something interesting with 's' here on the server.
    return s;
  }
}

客户端更改

使客户端 RPC 接口受 XSRF 保护

客户端 RPC 接口可以使用以下方法之一标记为受 XSRF 保护

package com.example.foo.client;

  import com.google.gwt.user.client.rpc.XsrfProtectedService;

  public interface MyService extends XsrfProtectedService {
    public String myMethod(String s);
  }
  • 通过使用 @XsrfProtect 注释显式注释接口或方法。可以使用 @NoXsrfProtect 注释在方法或服务上禁用 XSRF 防护,以禁用 XSRF 防护
package com.example.foo.client;

  import com.google.gwt.user.client.rpc.RemoteService;
  import com.google.gwt.user.server.rpc.XsrfProtect

  @XsrfProtect
  public interface MyService extends RemoteService {
    public String myMethod(String s);
  }

方法级注释会覆盖 RPC 接口级注释。如果不存在任何注释,并且 RPC 接口包含返回 RpcToken 或其实现的方法,则会在该接口的所有方法上执行 XSRF 令牌验证,除了返回 RpcToken 的方法以外。

提示:要指定 GWT 应为哪些 RpcToken 实现生成序列化程序,请使用 @RpcTokenImplementation 注释。

在 RPC 调用中包含 XsrfToken

要调用受 XSRF 保护的服务,客户端必须获取有效的 XsrfToken,并将其设置在服务端点上,方法是将服务的异步接口转换为 HasRpcToken,并调用 setRpcToken() 方法。

XsrfTokenServiceAsync xsrf = (XsrfTokenServiceAsync)GWT.create(XsrfTokenService.class);
((ServiceDefTarget)xsrf).setServiceEntryPoint(GWT.getModuleBaseURL() + "xsrf");
xsrf.getNewXsrfToken(new AsyncCallback<XsrfToken>() {

  public void onSuccess(XsrfToken token) {
    MyServiceAsync rpc = (MyServiceAsync)GWT.create(MyService.class);
    ((HasRpcToken) rpc).setRpcToken(token);

    // 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
    }
});

提示:如果您想为在 XsrfToken 验证期间生成的异常注册一个特殊的处理程序,请使用 HasRpcToken.setRpcTokenExceptionHandler()