我已经通过这种方式解决了 CSRF 问题:
我在服务器端创建令牌并通过 JSP 将其放置在 GWT 主机页面中。令牌也存储在 Session 中:
我的页面.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>
CSRFToken标签:
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;
}
}
}
它可能并不完美,但它似乎工作得很好!