我有一个使用Spring的REST API。我创建了一个拦截器:
@Component
public class CSRFInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// code here
return true;
}
}
每个请求都使用JSON和以下相应的Java类:
public class CSRFTokenContainer<T> {
private T data;
private String csrf;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getCsrf() {
return csrf;
}
public void setCsrf(String csrf) {
this.csrf = csrf;
}
}
在我的控制器中,一切运行良好,例如:
@Controller
@RequestMapping("/persons")
public class PersonController {
@RequestMapping(method=RequestMethod.POST)
public @ResponseBody String create(@RequestBody CSRFTokenContainer<Person> account, HttpServletResponse response) {
// do something
return "test";
}
}
现在我想执行以下操作:Controller应该只接收没有CSRF令牌的Person对象。应该在拦截器内处理CSRF令牌。我怎样才能做到这一点?我认为主要的问题是,我不知道如何在Interceptor中获取我的CSRFTokenContainer对象。之后我想修改请求只包含“数据”部分。
一些代码示例会很好。
谢谢!
答案 0 :(得分:0)
我用这种方式解决了CSRF问题:
我在服务器端创建令牌,并通过JSP将其放在GWT主页中。令牌也存储在Session:
中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;
}
}
}
它可能并不完美,但似乎效果很好!