Spring(Spring Boot)如何向前端提供 CSRF 令牌?

Posted

技术标签:

【中文标题】Spring(Spring Boot)如何向前端提供 CSRF 令牌?【英文标题】:How does Spring (Spring Boot) provide CSRF token to frontend? 【发布时间】:2021-03-15 17:51:02 【问题描述】:

我正在尝试将 CSRF 保护添加到基于 Spring-Boot(尤其是 WebFlux)的项目中。

到目前为止我所尝试的低于安全配置。

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) 

    http.
        ...
        .and()
        .csrf()
        .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
        .requireCsrfProtectionMatcher(
            new NegatedServerWebExchangeMatcher(
                new OrServerWebExchangeMatcher(
                    Arrays.asList(
                        ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, FORM_LOGIN_URL),
                        ServerWebExchangeMatchers
                            .pathMatchers(HttpMethod.GET, PERMISSION_ENDPOINT)
                    )
                )
            )
        )
        .accessDeniedHandler(new CustomHttpStatusServerAccessDeniedHandler(HttpStatus.FORBIDDEN))
        ...;

    return http.build();

但是当我尝试上述配置时,我看不到任何与从服务器推送到浏览器的 CSRF 令牌相关的内容。不是默认推送给客户端吗?如果是这样,向客户端(浏览器)提供 CSRF 令牌的更简洁的方法是什么?在哪个春季文档中提到了这一点。 (我关注了这个->https://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/csrf.html)

附: (希望以下这些信息对答案有用)

Spring Boot Webflux 应用程序没有提供我的登录表单。它由在不同域(不同 IP 和端口)上运行的 Angular 应用程序提供服务。这就是为什么我在上面的配置中有一个FORM_LOGIN_URL 常量,用作.loginPage(FORM_LOGIN_URL)。我已经自定义了 .authenticationSuccessHandler() 以重定向到 PERMISSION_ENDPOINT,它一直按预期工作。

【问题讨论】:

【参考方案1】:

这部分的文档记录有点差,但显然除非您订阅结果,否则 csrf-token 不会被保存。

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) 

   final CookieServerCsrfTokenRepository cookieServerCsrfTokenRepository = new CookieServerCsrfTokenRepository();
        cookieServerCsrfTokenRepository.setCookieHttpOnly(false);

    http.csrf()
            .csrfTokenRepository(cookieServerCsrfTokenRepository)
            .and()
            .cors()

             ...


// Add a tiny webfilter that just subscribes
@Bean
public WebFilter addCsrfToken() 
    return (exchange, next) -> exchange.<Mono<CsrfToken>>getAttribute(CsrfToken.class.getName())
                .doOnSuccess(token -> ) // do nothing, just subscribe :/
                .then(next.filter(exchange));


如果您使用功能端点,上述解决方案效果很好。您也可以使用@ControllerAdvice 方法。

@ControllerAdvice
public class SecurityControllerAdvice @ModelAttribute Mono<CsrfToken> csrfToken(ServerWebExchange exchange) 
        Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());
        return csrfToken.doOnSuccess(token -> exchange.getAttributes()
                .put(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME, token));
    


在 github 上有一个开放的错误报告

CookieServerCsrfTokenRepository does not add cookie

【讨论】:

谢谢老兄!在过去的几天里,这对我来说是一个巨大的头痛。无论如何,让我再问你一个问题。你是如何在 github 上找到这个开放的错误报告的?当你偶然发现这样的问题时,你有没有什么技巧,如果你分享它很高兴;)。再次感谢! naah,它只是 google man.. 我通常会非常彻底地阅读 spring 文档,我会查看源代码中的内容,然后我 google 并阅读。一旦您开始了解事物如何协同工作,spring 文档就会变得如此有趣和好。不久前我建立了一个登录系统,遇到了和你一样的问题,我用谷歌搜索了CookieServerCsrfTokenRepository github,这是第二次点击。 :D 好吧,下次我会记住您提到的内容。再次感谢伙计!

以上是关于Spring(Spring Boot)如何向前端提供 CSRF 令牌?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 的静态资源处理

如何使用 graphql-spring-boot 向 GraphQL Java 添加检测?

spring boot 结合啥前端框架

Angular2 和 Spring Boot。如何服务前端?

spring-boot如何去获取前端传递的参数

spring-boot如何去获取前端传递的参数