用于 Spring Security 和 AngularJS 的 CSRF/XSRF 保护

Posted

技术标签:

【中文标题】用于 Spring Security 和 AngularJS 的 CSRF/XSRF 保护【英文标题】:CSRF/XSRF protection for Spring Security and AngularJS 【发布时间】:2018-02-18 03:05:39 【问题描述】:

我尝试向我的应用程序添加 CSRF/XSRF 保护,但遇到了奇怪的行为。所有获取请求都可以正常工作,但是在所有发布/放置/删除时,我都会收到 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;
        

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

【问题讨论】:

【参考方案1】:

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

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

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

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

【讨论】:

【参考方案2】:

假设您的配置/过滤器的其余部分正常工作,您将因此面临这个问题:SessionCreationPolicy.STATELESS

您可以了解 Spring CsrfFilter 的幕后花絮。您会看到它需要记住会话中每个用户的每个 CSRF-token 的值,并且由于您没有使用会话,因此无法完成。

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

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

希望对你有帮助。

【讨论】:

出于这个原因,我实现了我的自定义 CSRF 过滤器,但我的问题是请求甚至没有到达它。这很奇怪。 @AnarSultanov,你能分享你过滤器的代码吗?不过,这并不重要,因为您在 Spring 的 CsrfFilter 之后添加了过滤器。如果 Springs 过滤器无法匹配来自用户会话的令牌(因为没有会话而无法获取),它会调用accessDeniedHandler 并执行return(破坏过滤器链),这意味着它没有调用filterChain.doFilter(request, response);,所以你的过滤器实际上不能被调用。如果您正在实施自己的 CSRF 保护 - 那么,我想,您应该通过调用 .csrf().disable() 来禁用 Spring 的实施。 我发现了我的问题。我在 cookie 中有 XSRF-TOKEN,但是 Angular 不会将带有此令牌的标头添加到请求中。现在我正试图找出原因以及如何解决这个问题。【参考方案3】:

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

如答案中所述,我使用了 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 文档说:

“不会为跨域请求设置标头。”

为了解决这个问题,我不得不使用 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');
]);

希望我的回答对你有用。

【讨论】:

以上是关于用于 Spring Security 和 AngularJS 的 CSRF/XSRF 保护的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security 自定义登录功能不适用于 Spring Boot 和 REST api

用于 Spring Security 和/或 Spring BlazeDS 集成的集中式会话管理(和终止)系统

用于非授权连接的 Spring Security REST Api

用于身份验证的 Grails Spring Security X509 和用于权限的 LDAP

基于 SAML 的 SSO 用于身份验证和 LDAP 用于授权 - Spring Boot Security

CSS 样式表不适用于 Spring Security + Spring Boot + Thymeleaf