如何将 Spring Security 的 CSRF 功能用于无状态端点?

问题描述 投票:0回答:3

我正在将 Spring Security 与无状态 Web 服务结合使用。我想使用 Spring Security 3.2 中的 CSRF 功能。无状态网络应用程序可以实现这一点吗?

这是相关的Java配置,因为我不得不暂时禁用CSRF。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .sessionFixation().none().and()
            .csrf().disable();
}
java spring-security csrf
3个回答
3
投票

如果你的服务确实是无状态的,CSRF 保护可能根本没有意义。只要服务器不使用 cookie 来识别/验证用户,您的服务就不易受到 CSRF 攻击。

详细说明请参见http://sitr.us/2011/08/26/cookies-are-bad-for-you.html


0
投票

如果您的应用程序或API要通过浏览器访问,则需要CSRF保护。

参考演示:spring-security-and-angular-js

https://spring.io/guides/tutorials/spring-security-and-angular-js/#_the_login_page_angular_js_and_spring_security_part_ii


0
投票

就像其他人所说,只有当您的应用程序将令牌存储在 cookie 中时才需要这样做。我最近不得不使用 Spring Security 6 来实现这一点,所以这就是我所采用的(用于自己进行身份验证的应用程序的解决方案)。

我发现使用自定义过滤器从 Spring Security 完全禁用 CSRF 广告检查(如本文章中所做的那样)的问题是,它还会关闭撤销 onAuthentication cookie,这可能允许会话固定攻击 - 攻击者在其中

  • 将 CSRF 令牌注入受害者的浏览器
  • 等待受害者登录
  • 使用另一个表单 POST 让受害者使用仍未更改的 CSRF 令牌执行操作。

由于这个原因,CSRF 代币应始终在本金更改时更改。

但是对于无状态应用程序,onAuthentication 会在每个请求上触发 - 这也不起作用,因为这将使前端在每个其他请求中都没有 CSRF 令牌。

解决方案:

根据 Spring Security Docu for SPA,我实现了

CsrfCookieFilter
CsrfTokenRequestAttributeHandler
,然后将其添加到
HttpSecurity

public class CsrfCookieFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
        // Render the token value to a cookie by causing the deferred token to be loaded
        csrfToken.getToken();
        filterChain.doFilter(request, response);
    }
}
public class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
    private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();

    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       Supplier<CsrfToken> csrfToken) {
        /*
         * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
         * the CsrfToken when it is rendered in the response body.
         */
        this.delegate.handle(request, response, csrfToken);
    }

    @Override
    public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
        /*
         * If the request contains a request header, use CsrfTokenRequestAttributeHandler
         * to resolve the CsrfToken. This applies when a single-page application includes
         * the header value automatically, which was obtained via a cookie containing the
         * raw CsrfToken.
         */
        if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
            return super.resolveCsrfTokenValue(request, csrfToken);
        }
        /*
         * In all other cases (e.g. if the request contains a request parameter), use
         * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
         * when a server-side rendered form includes the _csrf request parameter as a
         * hidden input.
         */
        return this.delegate.resolveCsrfTokenValue(request, csrfToken);
    }
}

然后我将这些添加到 HttpSecurity:

@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
    return http
        .csrf(csrf -> {
             CookieCsrfTokenRepository cookieCsrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
             cookieCsrfTokenRepository.setCookiePath("/");
                    
             csrf.csrfTokenRepository(cookieCsrfTokenRepository)
                 .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())
                 .sessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy()); // this turns OFF the onAuthenticate CSRF cookie revocation
         }) 
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
        .authorizeHttpRequests(customizer -> customizer.anyRequest().authenticated())
        .build();
}

然后,我在初始 JWT 令牌发行(因此是第一次登录)后手动触发

onAuthentication
,以便在主体更改时撤销 CSRF 令牌。为此,我首先创建以下 bean:

@Bean
public CookieCsrfTokenRepository cookieCsrfTokenRepository() {
    CookieCsrfTokenRepository cookieCsrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
    cookieCsrfTokenRepository.setCookiePath("/");
    return cookieCsrfTokenRepository;
}

@Bean
public CsrfAuthenticationStrategy csrfAuthenticationStrategy(CookieCsrfTokenRepository cookieCsrfTokenRepository) {
    return new CsrfAuthenticationStrategy(cookieCsrfTokenRepository);
}

以及初次登录后拨打电话

onAuthentication

// called in the method that does the initial login (after Tokens are first created)
csrfAuthenticationStrategy.onAuthentication(authentication, request, response);

这会撤销 Csrf-Token Cookie,客户端在登录后必须再次发出 GET 请求(应该如此)。


FrontSide(角度)解决方案:

@Injectable()
export class CsrfTokenInterceptor implements HttpInterceptor {
  /**
   * Methods allowed in Backend {@link CsrfFilter.DefaultRequiresCsrfMatcher#allowedMethods}
   */
  private readonly CSRF_ALLOWED_METHODS = ["GET", "HEAD", "TRACE", "OPTIONS"]
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const xsrfTokenFromCookie = this.getCookie('XSRF-TOKEN');
    // IF not a CSRF Allowed method (e.g. POST, PUT or DELETE), and XSRF-TOKEN is present
    if (!this.CSRF_ALLOWED_METHODS.includes(req.method) && xsrfTokenFromCookie) {
      // Clone the request and add X-XSRF-TOKEN header
      const modifiedReq = req.clone({
        headers: req.headers.set('X-XSRF-TOKEN', xsrfTokenFromCookie)
      });
      // Pass the modified request to the next handler
      return next.handle(modifiedReq);
    }
    // If it's a GET request, pass the original request
    return next.handle(req);
  }

  getCookie(name: string): string | undefined {
    const cookies = document.cookie.split(';');
    for (let cookie of cookies) {
      const [cookieName, cookieValue] = cookie.trim().split('=');
      if (cookieName === name) {
        return cookieValue;
      }
    }
    return undefined;
  }
}

当然不要忘记将其添加到提供者中:

...
providers: [
  {provide: HTTP_INTERCEPTORS, useClass: CsrfTokenInterceptor, multi:true}
],
...

此外,您还需要为将从后端获取 CSRF 令牌的请求添加选项

withCredentials: true

有一篇博客文章,更详细地解释了背景

© www.soinside.com 2019 - 2024. All rights reserved.