Spring Cloud Gateway+Spring Security中CSRF Token生成传递及权限配置问题
一、解决/auth路径的CSRF错误
你遇到的An expected CSRF token cannot be found错误,是因为Spring Security WebFlux默认开启了CSRF保护,所有POST/PUT/DELETE等修改类请求都会校验CSRF Token。但/auth/**是开放的注册接口,前端还未建立会话,自然无法获取Token,因此需要对该路径跳过CSRF校验。
修改你的SecurityConfig配置,添加CSRF忽略规则,同时调整路径匹配顺序(permitAll的路径要放在前面,匹配规则按顺序生效):
package com.sb.projects.medica.gateway.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.server.SecurityWebFilterChain; @Configuration @EnableWebFluxSecurity public class SecurityConfig { @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) { httpSecurity .authorizeExchange(exchanges -> exchanges .pathMatchers("/auth/**").permitAll() .pathMatchers("/doctors/**", "/patients/**").authenticated() .anyExchange().authenticated() // 兜底规则,确保未匹配的路径都需认证 ) .csrf(csrf -> csrf .ignoringPathMatchers("/auth/**") // 对/auth路径跳过CSRF校验 ) .oauth2Client() .and() .oauth2ResourceServer().jwt(); return httpSecurity.build(); } }
二、CSRF Token的生成与传递给前端
对于需要认证的/doctors/**和/patients/**路径,Spring Security WebFlux会自动生成CSRF Token并存储在WebSession中,你可以通过以下两种方式让前端获取并使用:
1. 通过Cookie传递Token(推荐)
配置CookieServerCsrfTokenRepository,让Token写入Cookie并允许前端读取,前端只需将Cookie中的Token值放在指定请求头即可:
修改SecurityConfig的CSRF配置部分:
.csrf(csrf -> csrf .ignoringPathMatchers("/auth/**") .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) // 设置HttpOnly为false,允许前端读取Cookie )
- 当用户完成认证后,服务端会在响应中设置名为
XSRF-TOKEN的Cookie; - 前端读取该Cookie的值,在发送POST/PUT/DELETE请求时,添加
X-XSRF-TOKEN请求头,值为Cookie中的Token内容,Spring Security会自动校验该头。
2. 自定义接口返回Token
如果不想用Cookie,可编写一个WebFlux接口,从WebSession中获取CSRF Token返回给前端:
import org.springframework.security.web.server.csrf.CsrfToken; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; @RestController public class CsrfController { @GetMapping("/csrf-token") public Mono<CsrfToken> getCsrfToken(Mono<CsrfToken> csrfToken) { return csrfToken; } }
- 前端在登录后调用该接口获取Token,后续请求时将Token放在
X-XSRF-TOKEN请求头中即可。
补充说明:如果你的接口仅通过JWT的Authorization头认证(JWT存储在localStorage而非Cookie),CSRF风险极低,可考虑直接关闭CSRF保护;但如果JWT存在HttpOnly Cookie中,仍建议保留CSRF保护。
内容的提问来源于stack exchange,提问作者Soumalya Bhattacharya

