在 Spring Cloud Gateway 后面服务的 Angular 应用程序无法向后端发送 POST 请求,因为 CSRF 令牌无效

问题描述 投票:0回答:1

我正在试验通过 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 的导入。

angular spring-boot spring-security csrf spring-cloud-gateway
1个回答
0
投票

简答: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应用程序也通过网关)

设置 CSRF cookie

从 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 令牌值没有被解析
.

正确验证 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);
© www.soinside.com 2019 - 2024. All rights reserved.