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

