You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3 Webflux中Spring Security禁用CSRF失效问题求助

问题描述

将Webflux REST API演示应用从Spring Boot 2.7.x版本升级至3.0.0后,测试POST请求时遇到Spring Security返回403 Forbidden错误,提示An expected CSRF token cannot be found。原有安全配置在Spring Boot 2.7.5中可正常工作,升级后失效。

安全配置代码

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    return http
        .csrf().disable()
        .authorizeExchange()
        .pathMatchers(HttpMethod.GET, "/actuator/**").permitAll()
        .pathMatchers(HttpMethod.POST, "/api/v1/users", "/api/v1/users/**").hasRole(ReactiveConstant.SECURITY_ROLE_ADMIN)     // Only admin can do POST
        .pathMatchers(HttpMethod.GET, "/api/v1/users", "/api/v1/users/**").hasAnyRole(ReactiveConstant.SECURITY_ROLE_USER, ReactiveConstant.SECURITY_ROLE_ADMIN)       // user can only do GET
        .anyExchange().authenticated()
        .and().formLogin()
        .and().httpBasic()
        .and().formLogin().disable()
        .build();
}

build.gradle 内容

plugins {
    id 'org.springframework.boot' version '3.0.0'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'java'
    id 'groovy'
}

group = 'io.c12.bala'
version = '0.2.1'
sourceCompatibility = JavaVersion.VERSION_17

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-security'

    // Springboot utils
    implementation 'io.projectreactor:reactor-tools'            // For Reactor debugging in IDE
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    implementation 'org.modelmapper:modelmapper:3.1.0'
    implementation 'io.netty:netty-resolver-dns-native-macos:4.1.85.Final:osx-aarch_64'     // For macos netty DNS issue.
    implementation 'com.aventrix.jnanoid:jnanoid:2.0.0'

    // Springboot testing with Spock test framework
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'

    // Spock test framework
    testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0'
    testImplementation 'org.spockframework:spock-spring:2.3-groovy-4.0'

    // Reactor test framework
    testImplementation 'io.projectreactor:reactor-test'
}

test {
    useJUnitPlatform()
    maxParallelForks = Runtime.runtime.availableProcessors()
}

测试请求与返回

测试POST请求:

curl --location --request POST 'http://localhost:8080/api/v1/users' \
--header 'Authorization: Basic am9objpIZWxsb1dvcmxkQDEyMw==' \
--header 'Content-Type: application/json' \
--data-raw '{
  "firstName": "John",
  "lastName": "Doe",
  "emailId": "John.doe@example.com",
  "userId": "j.doe"
}'

返回结果:

403 Forbidden
An expected CSRF token cannot be found
解决方案

问题根源在于Spring Boot 3.0对应的Spring Security 6.0对WebFlux的SecurityWebFilterChain链式调用逻辑做了调整,原有.and()链式写法会导致csrf().disable()的配置被后续操作覆盖,最终CSRF保护并未真正关闭。

修改安全配置为Spring Security 6推荐的lambda风格,确保每个配置独立生效:

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    return http
        // 先配置权限规则
        .authorizeExchange(exchanges -> exchanges
            .pathMatchers(HttpMethod.GET, "/actuator/**").permitAll()
            .pathMatchers(HttpMethod.POST, "/api/v1/users", "/api/v1/users/**").hasRole(ReactiveConstant.SECURITY_ROLE_ADMIN)
            .pathMatchers(HttpMethod.GET, "/api/v1/users", "/api/v1/users/**").hasAnyRole(ReactiveConstant.SECURITY_ROLE_USER, ReactiveConstant.SECURITY_ROLE_ADMIN)
            .anyExchange().authenticated()
        )
        // 明确关闭CSRF保护
        .csrf(csrf -> csrf.disable())
        // 启用HTTP Basic认证
        .httpBasic(Customizer.withDefaults())
        // 禁用表单登录
        .formLogin(formLogin -> formLogin.disable())
        .build();
}

关键说明

  • 使用lambda表达式替代.and()链式调用,避免配置被意外覆盖;
  • 调整配置顺序,优先定义权限规则,再配置CSRF、认证方式等;
  • 所有配置项通过lambda明确指定,逻辑更清晰,符合Spring Security 6的设计规范。

内容的提问来源于stack exchange,提问作者Bala

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 02:55:44