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

如何在Reactive WebFlux中优雅定制Spring Security CSRF防护规则?

Reactive Spring Boot中CSRF规则的定制化实现(排除特定Actuator端点)

问题场景

在混合Servlet/Reactive的Spring Boot微服务架构中,我们通过Actuator的POST /actuator/loggers端点动态调整日志级别(支持curl或Spring Boot Admin调用)。Servlet栈中可以通过ignoringRequestMatchers轻松排除该端点的CSRF校验,但Reactive栈没有等效API,直接使用requireCsrfProtectionMatcher会覆盖Spring Security默认的CSRF排除规则(比如认证相关端点的默认豁免)。

现有方案的局限

之前的解决方案通过组合AndServerWebExchangeMatcher和NegatedServerWebExchangeMatcher实现排除,但会覆盖ServerHttpSecure中默认配置的认证端点排除逻辑,导致原本不需要CSRF保护的认证接口也被拦截:

log.debug("Csrf is enabled");
http.csrf(
    csrf ->
        csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
            .requireCsrfProtectionMatcher(
                new AndServerWebExchangeMatcher(
                    CsrfWebFilter.DEFAULT_CSRF_MATCHER,
                    new NegatedServerWebExchangeMatcher(
                        EndpointRequest.to(LoggersEndpoint.class)))));

优化后的解决方案

方案1:Lambda匹配器(简洁版)

通过Lambda表达式在默认匹配逻辑基础上叠加自定义排除规则,既保留默认豁免,又新增特定端点排除:

log.debug("Csrf is enabled");
http.csrf(csrf -> csrf
    .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
    .requireCsrfProtectionMatcher(exchange -> {
        // 执行默认CSRF匹配逻辑(包含认证端点等默认豁免)
        Mono<Boolean> defaultShouldProtect = CsrfWebFilter.DEFAULT_CSRF_MATCHER.matches(exchange)
                .map(MatchResult::isMatch);
        // 检查是否为需要排除的Loggers端点
        Mono<Boolean> isExcluded = EndpointRequest.to(LoggersEndpoint.class).matches(exchange)
                .map(MatchResult::isMatch);
        // 只有默认需要保护,且不在排除列表中时,才触发CSRF校验
        return defaultShouldProtect.zipWith(isExcluded, (shouldProtect, exclude) -> shouldProtect && !exclude);
    })
);

方案2:自定义匹配器类(可复用版)

如果需要在多个服务中复用该规则,可以封装自定义ServerWebExchangeMatcher:

public class ExcludingCsrfMatcher implements ServerWebExchangeMatcher {
    private final ServerWebExchangeMatcher defaultMatcher = CsrfWebFilter.DEFAULT_CSRF_MATCHER;
    private final ServerWebExchangeMatcher excludedEndpoints = EndpointRequest.to(LoggersEndpoint.class);

    @Override
    public Mono<MatchResult> matches(ServerWebExchange exchange) {
        return defaultMatcher.matches(exchange)
                .flatMap(defaultMatch -> {
                    // 默认不需要保护的场景直接返回不匹配
                    if (!defaultMatch.isMatch()) {
                        return MatchResult.notMatch();
                    }
                    // 检查是否在排除列表中,是则豁免CSRF
                    return excludedEndpoints.matches(exchange)
                            .map(excludedMatch -> excludedMatch.isMatch() 
                                    ? MatchResult.notMatch() 
                                    : MatchResult.match());
                });
    }
}

配置时直接使用自定义匹配器:

log.debug("Csrf is enabled");
http.csrf(csrf -> csrf
    .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
    .requireCsrfProtectionMatcher(new ExcludingCsrfMatcher())
);

方案说明

这两种方案都基于Spring Security的默认CSRF匹配逻辑进行扩展,不会覆盖原生的认证端点豁免规则。核心思路是:先判断是否符合默认的CSRF保护条件,再额外检查是否属于需要排除的端点,只有同时满足「默认需要保护」且「不在排除列表」时,才触发CSRF校验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 05:34:57