1

我有一个使用 Spring 的 REST API。我创建了一个拦截器:

@Component
public class CSRFInterceptor extends HandlerInterceptorAdapter {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // code here

        return true;
    }
}

发出的每个请求都使用带有以下相应 Java 类的 JSON:

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";
    }
}

现在我想做以下事情:控制器应该只接收没有 CSRF 令牌的 Person 对象。CSRF 令牌应该在拦截器内部进行处理。我怎样才能做到这一点?我认为主要问题是,我不知道如何在 Interceptor 中获取我的 CSRFTokenContainer 对象。之后,我想将请求修改为仅包含“数据”部分。

一些代码示例会很好。

谢谢!

4

1 回答 1

0

我已经通过这种方式解决了 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;
        }
    }
}

它可能并不完美,但它似乎工作得很好!

于 2012-07-07T20:49:11.340 回答