我正在试验通过 Spring Cloud Gateway 为 Angular 应用程序提供服务的可能性。
GET 请求一切正常,但是当我尝试通过网关向资源服务发送 POST 请求时,请求因“无效的 CSRF 令牌”而失败。
这可能是因为配置错误,但我找不到它。 此外,Spring Gateway 和 Angular 的在线文档对于这种特定情况并不是很有用。
如果您想复制此行为,这是具有当前配置的存储库:github repo.
当前行为:来自 Angular 的 POST 请求因“无效的 CSRF 令牌”而失败:
error: "Invalid CSRF Token"
message: "Http failure response for http://gateway:8000/api/post: 403 Forbidden"
name: "HttpErrorResponse"
ok: false
status: 403
statusText: "Forbidden"
url: "http://gateway:8000/api/post"
期望的行为:POST 请求在 spring security 中没有禁用 csrf 的情况下成功执行。
这是我的网关 Spring Security 配置:
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) throws Exception {
return http
.authorizeExchange(exchange -> exchange
.pathMatchers("/**").permitAll()
.anyExchange().authenticated())
.csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()))
.build();
}
}
Gateway 还包含以下 WebFilter:
@Component
@Configuration
public class CsrfCookieWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String key = CsrfToken.class.getName();
Mono<CsrfToken> csrfToken = null != exchange.getAttribute(key) ? exchange.getAttribute(key) : Mono.empty();
return csrfToken.doOnSuccess(token -> {
ResponseCookie cookie = ResponseCookie.from("XSRF-TOKEN", token.getToken())
.maxAge(Duration.ofHours(1))
.httpOnly(false)
.path("/")
.sameSite(Cookie.SameSite.LAX.attributeValue())
.build();
exchange.getResponse().getCookies().add("XSRF-TOKEN", cookie);
}).then(chain.filter(exchange));
}
}
角度应用程序 AppModule 包含 HttpClientXsrfModule 的导入。
简答:RTFM 并仔细检查您导入的
CsrfToken
(有一个用于 WebMVC,另一个与另一个用于 WebFlux 的包不同)。
此外,与 Angular 应用程序一样,仔细检查
X-XSRF-TOKEN
标头是否正确定位(如果请求 URI 是绝对的,Angular 不会设置此标头:使用 this.httpClient.post('/api/post', {}, {observe: 'response'})
而不是 this.httpClient.post('http://gateway:8000/api/post', {}, {observe: 'response'})
,这需要服务 Angular应用程序也通过网关)
从 Spring Boot 3 (spring-security 6) 开始,必须提供过滤器以将 CSRF cookie 添加到响应中。
Cookie(Server)CsrfTokenRepository
已经不够用了
这在 here 用于 servlet 和 there 用于反应式应用程序(第二个是配置
spring-cloud-gateway
时要参考的那个)。 此文档包含在每种情况下复制/粘贴的确切配置.
此外,根据您的应用程序的性质,非常小心地导入正确的
CsrfToken
,否则令牌将为空:请求/交换属性具有CsrfToken.class.getName()
作为名称,您可以从org.springframework.security.web.csrf
或导入org.springframework.security.web.server.csrf
没有任何编译错误(第一个是在servlet中使用,第二个是在webflux中使用)。 CSRF cookie 不是由我的网关实例设置的,直到我意识到我已经在我的 CsrfCookie
中导入了 WebFilter
的 servlet 版本 => CSRF 令牌值没有被解析.
如文档中所述,应将
handle
的 Xor(Server)CsrfTokenRequestAttributeHandler
方法用作 csrf 请求处理程序(仅 handle
方法,而不是完整的 Xor(Server)CsrfTokenRequestAttributeHandler
实例)
spring-cloud-gateway
@Bean
WebFilter csrfCookieWebFilter() {
return (exchange, chain) -> {
// Make sure you import
// org.springframework.security.web.server.csrf.CsrfToken
// and not
// org.springframework.security.web.csrf.CsrfToken
Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
return csrfToken.doOnSuccess(token -> {
}).then(chain.filter(exchange));
};
}
在
SecurityWebFilterChain
:
var delegate = new XorServerCsrfTokenRequestAttributeHandler();
http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(delegate::handle);