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

如何将Web安全过滤器中的if逻辑改写为WebFlux响应式实现

WebFlux 响应式白名单过滤器改写方案

先修正现有代码的问题

首先现有代码里有两处需要先调整:

  • usernameWhitelisted方法不需要接收Mono<String>类型的用户名,应该在拿到实际用户名值之后再调用该方法查询Redis
  • 路径匹配不要使用阻塞的AntPathMatcher,改用WebFlux原生适配的PathPatternParser

完整实现代码

@Component
@Slf4j
public class WhitelistingFilter implements WebFilter {
  private static final String SECURITY_PROPERTIES = "security.properties";
  public final List<String> skipFilterUrls =
      List.of(USER_LOGIN_URL, ADMIN_LOGIN_URL, SIGNUP_BY_ADMIN_URL, SIGNUP_URL, LOGOUT_URL);
  private final PathPatternParser pathPatternParser = new PathPatternParser();

  private final Properties securityProperties = readConfigurationFile(SECURITY_PROPERTIES);
  private final String whitelistingEnabled = securityProperties.getProperty("whitelisting.enabled", Boolean.FALSE.toString());
  private final ReactiveRedisOperations<String, Object> whitelistingRedisTemplate;
  private final AuthenticationManager authenticationManager;

  public WhitelistingFilter(
      @Qualifier("reactiveWhitelistingRedisTemplate")
          ReactiveRedisOperations<String, Object> whitelistingRedisTemplate,
      AuthenticationManager authenticationManager) {
    this.whitelistingRedisTemplate = whitelistingRedisTemplate;
    this.authenticationManager = authenticationManager;
  }

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    // 1. 先判断是否需要跳过过滤:路径匹配到免过滤列表 或 白名单功能关闭
    boolean uriNeedSkip = skipFilterUrls.stream()
        .anyMatch(pattern -> pathPatternParser.parse(pattern)
            .matches(exchange.getRequest().getPath().pathWithinApplication()));
    if (uriNeedSkip || whitelistingDisabled()) {
      return chain.filter(exchange);
    }

    // 2. 响应式流串联校验逻辑
    return ReactiveSecurityContextHolder.getContext()
        .map(SecurityContext::getAuthentication)
        .map(Authentication::getName)
        // 空用户名兜底
        .defaultIfEmpty("")
        .flatMap(username -> {
          if (username.isBlank()) {
            return Mono.just(false);
          }
          // 查询Redis判断是否在白名单
          return usernameWhitelisted(username);
        })
        .flatMap(isWhitelisted -> {
          if (isWhitelisted) {
            log.info("用户已在白名单中,放行请求");
            return chain.filter(exchange);
          }
          log.error("用户不在白名单或用户名为空,返回401");
          exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
          return exchange.getResponse().setComplete();
        })
        // 统一异常处理
        .onErrorResume(e -> {
          log.error("白名单校验发生异常", e);
          return ReactiveSecurityContextHolder.clearContext()
              .then(Mono.defer(() -> {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
              }));
        });
  }

  private boolean whitelistingDisabled() {
    // 白名单开关是启动时加载的固定配置,不需要返回Mono,直接返回boolean即可
    return Boolean.FALSE.toString().equalsIgnoreCase(whitelistingEnabled);
  }

  private Mono<Boolean> usernameWhitelisted(String username) {
    return whitelistingRedisTemplate.hasKey(WHITELISTING_PREFIX + username);
  }
}

核心逻辑说明

  • 所有异步逻辑都通过Reactor的操作符串联,不需要手动拆Mono做判断,避免阻塞
  • 路径匹配逻辑放在响应式流外面直接判断,因为路径是当前请求可以直接拿到的静态值,不需要异步处理
  • 异常统一用onErrorResume捕获,避免异步异常漏处理导致请求挂起
  • 没有权限时直接设置响应状态后调用setComplete()返回,不需要再走后续过滤器链

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 06:54:03