Spring REST 后端的安全 CSRF 保护 - 将 Synchronizer Token Pattern 传输到客户端

Spring Security CSRF protection of REST backend - transfer Synchronizer Token Pattern to the client

我阅读了很多关于 Spring 安全 CSRF 保护的内容,但我仍然有点挣扎。现在文档和往常一样很棒,但它完全基于您在服务器上呈现 html 代码并能够向每个表单添加隐藏字段的想法。现在,因为我使用 AngularJS 和 JavaScript 调用后端,所以这不是一个真正的选择。

那么在这种情况下(Rest Backend / AngularJS frontend)实际获取 Token 到客户端的最佳方式是什么? AngularJS 似乎在 $resource 中内置了对 CSRF 的支持,并期望一个名为 "XSRF-TOKEN" 的 Cookie 检索令牌并在进一步的请求中将其作为 http header "X-XSRF-TOKEN" 发送。所以每个请求都将包含 http header,以及 cookie。现在在服务器端,我可以读取 header 并将其与我存储在 session.

中的令牌进行比较

我遇到的问题是,它看起来有点复杂。由于登录本身必须受到保护,因此需要为 CSRF 令牌创建一个临时 session。这真的有必要吗?

也许这只是一个愚蠢的问题,但为什么我不能在客户端创建一个 random-token 并在客户端将其设置为 HTTP header 和 cookie。这类似于“OWASP double submit cookie”,但在 client-side 上生成令牌。这样服务器就不需要在登录前有一个 session,因为他可以只比较 2 个提交的令牌。现在,虽然攻击者可以发送 HTTP header,但他 same-origin-policy 无法读取或设置 cookie,并且只要数字几乎无法猜测,他就无法获得匹配项。

现在本能地在客户端生成安全令牌对我来说似乎很危险,我想我可以避免它..但是为什么呢?我觉得我错过了什么,肯定有充分的理由 Spring 安全将令牌存储在 session 中,对吗?

请赐教:)

我最终使用了 spring-security-csrf-token-interceptor-extended,它从 http-header "X-CSRF-TOKEN"(名称是可配置的)读取 CSRF-Token 并将其作为 http-header进一步请求。

现在我唯一要做的就是让 Spring-Security 将令牌作为 HTTP Header 发送(因为我不在服务器端呈现 html 代码,因此可以'不要将其添加为隐藏字段)。

 <security:http ....
     <security:custom-filter ref="csrfTokenResponseHeaderBindingFilter" after="CSRF_FILTER"/>
 ....
 </security:http>

过滤器基本上在正常 CSRF_FILTER 之后运行并读取“_csrf”request-attribute(由 CSRF_FILTER 放在那里)并将其设置为 header "X-CSRF-TOKEN"

public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter {
    protected static final String REQUEST_ATTRIBUTE_NAME = "_csrf";
    protected static final String RESPONSE_TOKEN_NAME = "X-CSRF-TOKEN";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, javax.servlet.FilterChain filterChain) throws ServletException, IOException {
        CsrfToken token = (CsrfToken) request.getAttribute(REQUEST_ATTRIBUTE_NAME);

        if (token != null) {
            response.setHeader(RESPONSE_TOKEN_NAME, token.getToken());
        }

        filterChain.doFilter(request, response);
    }
}