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

Spring Webflux Security通过Google OAuth2登录后未附加CSRF Cookie

问题

我正在为Spring Webflux后端和ReactJS前端实现基于Google OAuth2的简易登录功能,目前通过OpenAPI Swagger页面测试API。登录功能已接近完成,但存在一个问题:通过Google登录后,API响应仅附加SESSION Cookie,未附带X-CSRF Cookie。因此,对受保护端点的GET请求可正常工作,但POST请求因缺少可被JS访问的X-CSRF Cookie作为请求头而失败。

具体来说,请求http://localhost:8080/login/oauth2/code/google?state=...&code=...&scope=email+profile+......返回302状态码及以下响应头:

  • Location: /swagger-ui.html(重定向至Swagger UI页面)
  • Set-Cookie: SESSION=<session-id>(标识已登录)
    但没有X-CSRF Token的Set-Cookie头。

令人困惑的是,访问Spring的/logout页面时,响应会通过Set-Cookie附加X-CSRF Token,但这并无实际用处,我希望在设置SESSION Cookie的登录响应中同时返回X-CSRF Cookie。

补充说明:我的Swagger UI在获取到X-CSRF Cookie后可正常工作——先访问/logout页面获取该Cookie,返回Swagger UI后就能正确将其作为请求头使用。

请问我的配置中缺少了什么?以下是我的配置代码:

SecurityConfig.kt

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {
    @Bean
    fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http
            .csrf { csrf -> csrf
                .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
                .csrfTokenRequestHandler(ServerCsrfTokenRequestAttributeHandler())
            }
            .authorizeExchange { exchange ->
                exchange
                    .anyExchange()
                    .authenticated()
            }
            .oauth2Login(Customizer.withDefaults())
            .build()
    }
}

application.yml

springdoc:
  swagger-ui:
    enabled: true
    csrf:
      enabled: true
      header-name: "X-XSRF-TOKEN"
      cookie-name: "XSRF-TOKEN"
    url: /my-open-api-spec.yaml
    path: /swagger-ui.html
  api-docs:
    enabled: true
    path: /api-docs

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${google_client_id}
            client-secret: ${google_client_secret}
            scope:
              - email
              - profile

Application.kt

@SpringBootApplication
@EnableR2dbcAuditing
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}

HttpRouter.kt

@Configuration
class HttpRouter(val userHandler: UserHandler) {

    @Bean
    fun routes(): RouterFunction<ServerResponse> {
        return router {
            "/users".nest {
                GET("/me", userHandler::getMyUserDetails)
                POST("", userHandler::inviteUser)
                GET("", userHandler::searchUsers)
            }
        }
    }
}
解决方案

问题核心是OAuth2登录完成后的302重定向响应中,Spring Security默认不会主动生成并携带CSRF Token Cookie。需要通过自定义认证成功处理器,在登录完成后手动生成并设置CSRF Token到响应Cookie中。

步骤1:自定义OAuth2登录成功处理器

创建CustomOAuth2LoginSuccessHandler类,实现ServerAuthenticationSuccessHandler接口,在登录成功时生成CSRF Token并写入响应Cookie:

import org.springframework.security.core.Authentication
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono

class CustomOAuth2LoginSuccessHandler(
    private val csrfTokenRepository: ServerCsrfTokenRepository
) : ServerAuthenticationSuccessHandler {

    override fun onAuthenticationSuccess(
        exchange: ServerWebExchange,
        authentication: Authentication
    ): Mono<Void> {
        return csrfTokenRepository.generateToken(exchange)
            .flatMap { csrfToken ->
                csrfTokenRepository.saveToken(exchange, csrfToken)
                    .then(Mono.fromRunnable {
                        exchange.response.addCookie(csrfTokenRepository.toCookie(csrfToken))
                    })
            }
            .then(Mono.defer {
                // 保留原重定向逻辑,默认跳转到Swagger UI
                val redirectUrl = exchange.request.queryParams.getFirst("redirect_uri") ?: "/swagger-ui.html"
                exchange.response.statusCode = org.springframework.http.HttpStatus.FOUND
                exchange.response.headers.location = java.net.URI.create(redirectUrl)
                Mono.empty()
            })
    }
}

步骤2:更新SecurityConfig配置

在SecurityConfig中注入CSRF Token仓库实例,并将自定义处理器配置到oauth2Login流程中:

@Configuration
@EnableWebFluxSecurity
class SecurityConfig {

    @Bean
    fun csrfTokenRepository(): ServerCsrfTokenRepository {
        return CookieServerCsrfTokenRepository.withHttpOnlyFalse()
    }

    @Bean
    fun springSecurityFilterChain(
        http: ServerHttpSecurity,
        csrfTokenRepository: ServerCsrfTokenRepository
    ): SecurityWebFilterChain {
        val successHandler = CustomOAuth2LoginSuccessHandler(csrfTokenRepository)

        return http
            .csrf { csrf -> csrf
                .csrfTokenRepository(csrfTokenRepository)
                .csrfTokenRequestHandler(ServerCsrfTokenRequestAttributeHandler())
            }
            .authorizeExchange { exchange ->
                exchange
                    .anyExchange()
                    .authenticated()
            }
            .oauth2Login { oauth2 ->
                oauth2.authenticationSuccessHandler(successHandler)
            }
            .build()
    }
}

关键说明

  • 自定义处理器在登录认证完成后,通过csrfTokenRepository生成并保存CSRF Token,再将Token写入响应Cookie,确保登录后的302响应同时携带SESSION和XSRF-TOKEN。
  • 保留了原有的重定向逻辑,确保登录后依然跳转到指定页面(默认Swagger UI)。
  • 修改后,前端可直接获取到X-CSRF Cookie,无需额外访问/logout页面,POST请求就能正常携带CSRF请求头通过校验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 08:30:20