java - CSRF token 不是使用 Webflux 生成的

标签 java spring spring-boot spring-security spring-webflux

我有一个由 Spring Security 保护的 Webflux 应用程序,其中默认启用 CSRF 保护。但是,我无法将 CSRF token 保存在 session 中。

经过一番调查,我注意到它可能来自WebSessionServerCsrfTokenRepository.class。在此类中,有一个 generateToken 方法,该方法应从生成的 CSRF token 创建 Mono:

public Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
        return Mono.fromCallable(() -> {
            return this.createCsrfToken();
        });
    }

private CsrfToken createCsrfToken() {
        return new DefaultCsrfToken(this.headerName, this.parameterName, this.createNewToken());
    }

    private String createNewToken() {
        return UUID.randomUUID().toString();
    }

但是,即使CsrfWebFilter调用了generateToken方法,createCsrfToken方法也永远不会被调用,并且我永远不会得到CSRF要保存在 session 中的 token 。我的断点从未进入 createCsrfToken 方法,这可能意味着它从未被订阅。

我在 Netty 上运行 Spring Boot 2.1.0.RELEASE 和 Spring Security 5.1.1.RELEASE

我在一个仅包含以下依赖项的空示例应用程序上重现了该问题:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>

我是否遗漏了什么,或者 Spring Security 有问题吗?

更新

经过进一步调查,我认为问题出在Spring Security CsrfWebFilter.class中的这个方法:

private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
        return Mono.defer(() -> {
            Mono<CsrfToken> csrfToken = this.csrfToken(exchange);
            exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken);
            return chain.filter(exchange);
        });
    }

这里,csrfToken Mono 永远不会被订阅。当我以这种方式重写过滤器时,我设法在 session 中添加 token :

private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
        return Mono.defer(() -> {
            return this.csrfToken(exchange)
                    .map(csrfToken -> exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken))
                    .then(chain.filter(exchange));
        });
    }

但是,_csrf 参数从未添加到我的 Thymeleaf 模型中,因此以下测试不起作用:

<form name="test-csrf" action="/test" method="post">
            <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
            <button type="submit">Escape!</button>
        </form>

最佳答案

如果有人遇到这个问题,我与 Spring 团队的某人进行了讨论。

它实际上并不打算直接订阅csrfToken Mono,而是仅在需要时才订阅。开发者有责任在应用程序中触发订阅,有两种方法可以实现。

方法一:显式订阅。

通过某些@ControllerAdvice或抽象 Controller 类中的@ModelAttribute提供订阅:

@ModelAttribute(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME)
    public Mono<CsrfToken> getCsrfToken(final ServerWebExchange exchange) {

        return exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
    }

方法二:使用Thymeleaf自动处理CSRF。

确保 POM 中有以下依赖项,以便将 Thymeleaf 与 Spring Security 一起使用:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

这会自动将 CSRF token 添加到您的模型中,并通过表单隐藏输入传递它(仍需要将其添加到 POST Ajax 请求的 header 中)。

有关更多信息,这是我在 Spring 上打开的问题:https://github.com/spring-projects/spring-security/issues/6046

关于java - CSRF token 不是使用 Webflux 生成的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53144055/

相关文章:

java - 从 spring 属性加载 SQL 不运行

java - 在 Spring Boot 中使用嵌入式 stub /虚拟应用程序进行测试

java - Spring Boot 处理 SizeLimitExceededException

java - 如何在文本框中输入左括号

java - 从 Activity 中调用 fragment 中的非静态方法?

java - 在同一个工作区同时使用Intellij和netbeans

java - JNI : Is it safe to make java object a C++ class member?

java - 在 Tomcat/Spring 中映射静态和动态文件?

Spring WebFlux - bodyType=org.springframework.web.multipart.MultipartFile 不支持内容类型 'application/xml'

java - 使用 JNDI 在 Spring Boot 中配置多个数据源