Thymeleaf Webflux 安全,Csrf 未添加到视图中

Posted

技术标签:

【中文标题】Thymeleaf Webflux 安全,Csrf 未添加到视图中【英文标题】:Thymeleaf Webflux Security, Csrf not added to views 【发布时间】:2018-11-27 04:14:11 【问题描述】:

我正在尝试将 csrf 标签添加到表单中,但它的工作方式似乎与在 mvc 中不同。

所以我所做的是添加 <input type="hidden" th:name="$_csrf.parameterName" th:value="$_csrf.token" />

登录表单,但 _csrf 属性不存在,即使这些注释存在

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity

这是我的 SecurityWebFilterChain:

 http
                .authorizeExchange().pathMatchers(
                "/landing",
                "/",
                "/register",
                "/login",
                "/favicon.ico",
                "/js/**",
                "/fonts/**",
                "/assets/**",
                "/css/**",
                "/webjars/**").permitAll()
                .anyExchange().authenticated()
                .and()
                .httpBasic()
                .and()
                .formLogin().loginPage("/login")
                .and().logout()

我错过了什么?

更新: 添加了我正在使用的相关依赖项。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
    <thymeleaf-layout-dialect.version>2.0.0</thymeleaf-layout-dialect.version>

</properties>
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>net.sourceforge.nekohtml</groupId>
            <artifactId>nekohtml</artifactId>
            <version>1.9.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity4</artifactId>
            <version>3.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>io.github.jpenren</groupId>
            <artifactId>thymeleaf-spring-data-dialect</artifactId>
            <version>3.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.9.RELEASE</version>
        </dependency>
    </dependencies>

更新 当我在登录表单中包含带有 csrf 的隐藏输入标签时:

<input type="hidden" th:name="$_csrf.parameterName" th:value="$_csrf.token" />

我收到此错误:

org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "_csrf.parameterName" (template: "public/login" - line 75, col 17)

Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'parameterName' cannot be found on null

因为 _csrf 出于某种原因为空,即使注释已到位。

登录控制器:

 @GetMapping("/login")
    public String login(Model model) 
        return "public/login";
    

还尝试添加这样的控制器建议:

@ControllerAdvice
public class SecurityAdvice 

    @ModelAttribute("_csrf")
    Mono<CsrfToken> csrfToken(final ServerWebExchange exchange) 

        final Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());

        return csrfToken.doOnSuccess(token -> exchange.getAttributes()
                .put(DEFAULT_CSRF_ATTR_NAME, token)).log();
    

与这里使用的类似:https://github.com/daggerok/csrf-spring-webflux-mustache

然而这会导致

java.lang.NullPointerException: null
    at com.a.Config.SecurityAdvice.csrfToken(SecurityAdvice.java:23) ~[classes/:na]

这一行是最后一个sn-p的返回部分。

【问题讨论】:

您是否遇到任何错误? @SupunDharmarathne 更新了问题。 【参考方案1】:

适用于所有控制器的最简洁的解决方案是使用@ControllerAdvice,详细说明in the documentation:

@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));
    

【讨论】:

【参考方案2】:

这就是我为使其正常工作所做的。

@GetMapping("/login")
public Mono<String> login(ServerWebExchange exchange, Model model) 
    Mono<CsrfToken> token = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
    return token.map(t -> 
        model.addAttribute("_csrf", t);
        return "login";
    );

【讨论】:

关于这一点,你知道为什么当表单的 enctype 是多部分时我得到“无效的 CSRF 令牌”,但在其他情况下工作正常吗?我有时需要上传文件,这需要使用多部分。打开另一个问题:***.com/questions/52074742/…【参考方案3】:

我必须包含thymeleaf-extras-springsecurity5(而不是springsecurity4)才能使其正常工作。

我找不到任何 CsrfToken,这意味着来自 @mk88 的回答没有帮助,也没有帮助 this guide。

一旦我添加了以下依赖项,它就可以按设计完美运行​​:

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

Thymeleaf 的输出:

<form action="/members" method="POST" ><input type="hidden" name="_csrf" value="432033fd-1076-4620-9f99-d0220b6d3071"/>

【讨论】:

【参考方案4】:

在 WebFlux 中,默认情况下 CsrfToken 信息似乎并未实际添加到模型中。 (见https://github.com/spring-projects/spring-security/issues/6046)

解决方法是添加一个 ControllerAdvice 订阅令牌信息并通过 ComponentScan 引用它或将其声明为显式 bean,如下所示:

(我使用的是 Kotlin,但这应该在 Java 中以相同的方式工作)

import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME
import org.springframework.security.web.server.csrf.CsrfToken
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono

@ControllerAdvice
class CsrfControllerAdvice 

    @ModelAttribute(value = DEFAULT_CSRF_ATTR_NAME)
    fun csrfToken(exchange: ServerWebExchange): Mono<CsrfToken> 
        return exchange.getAttributeOrDefault(CsrfToken::class.java.name, Mono.empty())
    

【讨论】:

以上是关于Thymeleaf Webflux 安全,Csrf 未添加到视图中的主要内容,如果未能解决你的问题,请参考以下文章

Webflux禁用特定URL上的CSRF

Spring Webflux 服务器发送事件 Thymeleaf

无法使用 Spring Webflux 和 Thymeleaf 对静态资源进行版本控制

Webflux multipart/form-data,启用 csrf,有和没有文件上传获取 Invalid CSRF Token

在 Spring Webflux 中为给定路径禁用身份验证和 csrf?

在 Thymeleaf 中禁用 CSRF 隐藏输入