私はこのようにCSRFの問題を解決しました:
サーバー側でトークンを作成し、JSP を介して GWT ホスト ページ内に配置します。トークンはセッションにも保存されます。
myPage.jsp:
<%@taglib prefix="t" uri="myTags" %>
<!doctype html>
<html>
<head>
...
<script>
<t:csrfToken />
</script>
...
</head>
...
</html>
myTags.tld:
<?xml version="1.0" encoding="UTF-8"?>
<taglib xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>t</short-name>
<uri>myTags</uri>
<tag>
<name>csrfToken</name>
<tag-class>myapp.server.jsp.CSRFTokenTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
CSRFTokenTag:
public class CSRFTokenTag extends TagSupport {
private final SecureRandom random = new SecureRandom();
private String generateToken() {
final byte[] bytes = new byte[32];
random.nextBytes(bytes);
return Base64.encode(bytes);
}
@Override
public int doStartTag() throws JspException {
String token = generateToken();
try {
pageContext.getOut().write("var " + "myCSRFVarName" + " = \"" + token + "\";");
} catch (IOException e) {}
pageContext.getSession().setAttribute("csrfTokenSessionAttributeName", token);
return SKIP_BODY;
}
@Override
public int doEndTag() throws JspException {
return EVAL_PAGE;
}
}
GWT は JSNI 経由でトークンを読み取ります。
public class CSRFToken {
private native static String get()/*-{
return $wnd["myCSRFVarName"];
}-*/;
}
また、すべてのリクエストで、Web アプリケーションはカスタム HTTP ヘッダー内でトークンを送信します。たとえば、次のようになります。
RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, "/rest/persons");
rb.setHeader("myCSRFTokenHeader", CSRFToken.get());
rb.setRequestData("someData");
rb.setCallback(new RequestCallback() {
@Override
public void onResponseReceived(Request request, Response response) {
// ...
}
@Override
public void onError(Request request, Throwable exception) {
// ...
}
});
rb.send();
Spring 内で、リクエストごとに送信されたヘッダーからトークンを読み取り、それをチェックするインターセプターを作成しました。
@Component
public class CSRFInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String sessionCSRFToken = (String) request.getSession().getAttribute("csrfTokenSessionAttributeName");
if(sessionCSRFToken != null && sessionCSRFToken.equals(request.getHeader("myCSRFTokenHeader"))) {
return true;
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication required");
return false;
}
}
}
完璧ではないかもしれませんが、かなりうまく機能しているようです。