CSRF/XSRF 保护 Spring 安全和 AngularJS

CSRF/XSRF protection for Spring Security and AngularJS

我试图为我的应用程序添加 CSRF/XSRF 保护,但 运行 进入 st运行ge 行为。所有 get 请求都工作正常,但在所有 post/put/delete 上,我收到 403 Unauthorized。最重要的是,当我尝试调试我的 CSRF 过滤器时,请求没有到达它,它们在较早的某个地方被拒绝了。他们甚至没有达到我的身份验证过滤器,所以我无法弄清楚可能是什么问题。

我的安全配置:

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    ...
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
                    .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
                    .csrf().csrfTokenRepository(csrfTokenRepository());
        }

        private CsrfTokenRepository csrfTokenRepository() {
            HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
            repository.setHeaderName("X-XSRF-TOKEN");
            return repository;
        }

我没有添加过滤器,因为正如我所说,请求没有到达它们。但如果需要,我会完成我的问题。希望得到您的帮助,在此先感谢您!

假设您 config/filters 的其余部分工作正常,您会因为以下原因而面临此问题:SessionCreationPolicy.STATELESS

您可以深入了解 Spring CsrfFilter。您会看到它需要记住会话中每个用户的每个 CSRF 令牌的值,并且由于您没有使用会话,因此无法完成。

接下来要做什么 - 完全取决于您。有人说,如果你的应用是无状态的,实际上就不需要 CSRF 保护。 Spring docs 说 CSRF 攻击仍然相关。我认为这真的取决于您的身份验证机制。

例如,您可能还想查看 this nice article

希望对您有所帮助。

原则上,Spring 中的 CSRF 机制将 CSRF 令牌存储在 HTTP only cookie 中。因为 JavaScript 无法访问仅 HTTP cookie,您需要告诉 spring 禁用仅 HTTP:

.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());

然后您可以从 Angular 读取 cookie,并在每个请求中将其添加到 XSRF-TOKEN header。

这是一般情况。我不确定这是否适合您的特殊情况。

非常感谢您的回答,他们确实帮助我找到了解决方案。如果将来有人遇到同样的问题,我想分享我的解决方案。

如答案中所述,我使用 SessionCreationPolicy.STATELESS 并且没有会话,因此我不得不使用 CookieCsrfTokenRepositorywithHttpOnlyFalse() 来代替 HttpSessionCsrfTokenRepository 以允许 AngularJS 读取 cookie。

因此,我有这样的配置:

@Override
public void configure(HttpSecurity http) throws Exception {
    http
            ...
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
            .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
            .csrf().csrfTokenRepository(csrfTokenRepository());
}

如果有人对 CsrfHeaderFilter 的外观感兴趣:

public class CsrfHeaderFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        if (csrf != null) {
            Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
            String token = csrf.getToken();
            if (cookie==null || token!=null && !token.equals(cookie.getValue())) {
                cookie = new Cookie("XSRF-TOKEN", token);
                cookie.setPath("/");
                response.addCookie(cookie);
            }
        }
        filterChain.doFilter(request, response);
    }
}

我的第二个问题是 CORS。 AngularJS 文档说:

"The header will not be set for cross-domain requests."

为了解决这个问题,我不得不使用 HTTP 拦截器:

.factory('XsrfInterceptor', function ($cookies) {
  return {
    request: function (config) {
      var headerName = 'X-XSRF-TOKEN';
      var cookieName = 'XSRF-TOKEN';
      config.headers[headerName] = $cookies.get(cookieName);
      return config;
    }
  };
});

.config(['$httpProvider', function($httpProvider) {  
    $httpProvider.interceptors.push('XsrfInterceptor');
}]);

希望我的回答对你有用。