响应式编程下Spring Security过滤器仅登录后调用优化问询
WebFlux权限过滤器优化方案
核心优化方向
- 消除配置重复:过滤器与Security配置中重复定义的允许URL集合,统一维护避免不一致
- 简化流操作:小集合无需使用
parallelStream,改用普通Stream API提升效率 - 移除冗余原子类:Reactor订阅逻辑为单线程执行,无需
AtomicReference/AtomicInteger保证线程安全 - 优化URL匹配:利用Spring原生
PathMatcher统一处理通配符匹配,替代手动字符串切割 - 简化权限校验:用
anyMatch替代计数判断逻辑,提升代码可读性与性能 - 完善边界处理:增加未认证场景的兜底逻辑
优化后的CustomerWebFilter代码
@Log4j2 @Component @RequiredArgsConstructor public class CustomerWebFilter implements WebFilter { private final AccountRoleMenuViewEntityService accountRoleMenuViewEntityService; private final PathMatcher pathMatcher = new AntPathMatcher(); // 复用Security配置中的免认证规则,避免重复定义 private static final List<String> PERMIT_ALL_PATTERNS = Arrays.asList( "/login", "/logout", "/forgotpassword", "/sendcode", "/verifycode", "/newpassword", "/razorpay-webhook", "/assets/**", "/bootstrap/**", "/plugins/**" ); @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); String requestPath = request.getPath().pathWithinApplication().value(); // 跳过免认证URL if (PERMIT_ALL_PATTERNS.stream().anyMatch(pattern -> pathMatcher.match(pattern, requestPath))) { return chain.filter(exchange); } log.debug("执行权限过滤,请求路径: {}", requestPath); String urlPrefix = extractUrlPrefix(requestPath); return exchange.getPrincipal() // 兜底处理未认证场景 .switchIfEmpty(Mono.defer(() -> { response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); })) .flatMap(principal -> { log.debug("当前用户: {}", principal.getName()); // 根路径默认匹配dashboard前缀 String targetPrefix = "/".equals(urlPrefix) ? "/customer/dashboard" : urlPrefix; return accountRoleMenuViewEntityService.findByUsername(principal.getName()) // 检查是否存在匹配的权限前缀 .anyMatch(menu -> { String controller = menu.getController(); if (controller.contains("/") && controller.split("/").length >=3) { String controllerPrefix = "/" + controller.split("/")[1] + "/" + controller.split("/")[2]; log.debug("控制器前缀: {}, 请求前缀: {}", controllerPrefix, targetPrefix); return controllerPrefix.equalsIgnoreCase(targetPrefix); } return false; }) .flatMap(hasPermission -> { if (!hasPermission) { response.setStatusCode(HttpStatus.FORBIDDEN); return response.setComplete(); } return chain.filter(exchange); }); }); } /** * 提取URL二级前缀,例如 /customer/order/list -> /customer/order */ private String extractUrlPrefix(String requestPath) { if ("/".equals(requestPath) || requestPath.length() <= 1) { return "/"; } String[] pathSegments = requestPath.split("/"); return pathSegments.length >=3 ? "/" + pathSegments[1] + "/" + pathSegments[2] : requestPath; } }
配套优化的Security配置
新增统一常量类维护权限规则,彻底消除配置重复:
// 统一权限规则常量类 public class SecurityConstants { public static final List<String> PERMIT_ALL_PATTERNS = Arrays.asList( "/login", "/logout", "/forgotpassword", "/sendcode", "/verifycode", "/newpassword", "/razorpay-webhook", "/assets/**", "/bootstrap/**", "/plugins/**" ); }
修改Security配置类引用常量:
@Configuration @EnableWebFluxSecurity @RequiredArgsConstructor public class ConfigurationSecurity { private final CustomerWebFilter customerWebFilter; private final RememberMeSuccessHandler rememberMeSuccessHandler; @Bean public SecurityWebFilterChain configure(ServerHttpSecurity http) { http.csrf().disable() .headers().frameOptions().disable() .and() .authorizeExchange() .pathMatchers(SecurityConstants.PERMIT_ALL_PATTERNS.toArray(new String[0])).permitAll() .pathMatchers("/customer/**").hasRole("CUSTOMER") .anyExchange().authenticated() .and() .addFilterAfter(customerWebFilter, SecurityWebFiltersOrder.AUTHORIZATION) .formLogin() .loginPage("/login") .authenticationSuccessHandler(rememberMeSuccessHandler) .authenticationFailureHandler(new AuthenticationFailureHandler()) .and() .exceptionHandling() .accessDeniedHandler(new AccessDeniedHandler()) .and() .logout() .logoutUrl("/logout") .requiresLogout(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/logout")) .and() .httpBasic(); return http.build(); } @Bean ReactiveAuthenticationManager reactiveAuthenticationManager(AccountDataService accountDataService) { return new UserDetailsRepositoryReactiveAuthenticationManager(accountDataService); } @Bean public PasswordEncoder encoder() { return new BCryptPasswordEncoder(); } }
优化后优势
- 配置一致性:所有免认证URL规则集中维护,过滤器与Security配置共用,避免维护多份导致的不一致
- 高效匹配:用Spring原生
AntPathMatcher处理通配符匹配,替代手动字符串切割逻辑 - 逻辑简洁:用
anyMatch直接判断权限,替代原有的计数统计,代码更直观、性能更好 - 健壮性提升:增加未认证场景的兜底处理,返回401状态码,覆盖边界情况
- 性能优化:移除不必要的原子类,减少线程安全开销;小集合改用普通Stream操作,避免并行流的额外消耗
- 可读性增强:提取URL前缀处理方法,代码结构更清晰
内容的提问来源于stack exchange,提问作者Mitva Shah
相关产品推荐
相关产品推荐

