如何在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
相关产品推荐
相关产品推荐

