JAVA SSE接口开发中的Spring Security与异步线程池配置方法

 更新时间:2025年12月22日 14:42:33   作者:布伽思索  
本文详细描述了在使用SpringSecurity和异步线程池进行SSE接口开发时遇到的两个问题:AccessDeniedException异常和SimpleAsyncTaskExecutor警告,文章还介绍了线程池参数的科学估算方法,并适用于SpringBoot/SpringMVC的SSE接口生产环境配置,感兴趣的朋友一起看看吧

JAVA SSE接口开发中的Spring Security与异步线程池

本文记录了SSE(Server-Sent Events)接口开发场景中遇到的两个问题——Spring Security的AccessDeniedException异常SimpleAsyncTaskExecutor生产环境警告,提供从根源分析到落地实现的完整解决方案,同时附上线程池参数的科学估算方法,适用于Spring Boot/Spring MVC的SSE接口生产环境配置。

一、SSE接口的Spring Security权限异常解析与解决

1. 异常根源:异步请求的二次权限校验冲突

当使用注解中使用了produces = MediaType.TEXT_EVENT_STREAM_VALUE定义SSE接口时(列如:@PostMapping(value = “/chat”,produces = MediaType.TEXT_EVENT_STREAM_VALUE)),会触发Servlet异步处理机制,异常产生的完整链路如下:

  • 客户端发起请求,Spring Security拦截初始请求,权限校验通过后进入业务逻辑,通过SseEmitter向客户端流式发送数据;
  • 当SseEmitter调用complete()方法完成数据发送后,Servlet容器会触发一次DispatcherType.ASYNC类型的内部转发,这是Servlet规范的异步回调机制;
  • Spring Security默认拦截所有类型请求(包括ASYNC),但此时SSE响应已提交,且异步转发的请求上下文可能不完整(如请求路径信息缺失),导致权限校验失败并抛出AccessDeniedException,而响应已无法修改,最终仅能在日志中记录异常。

核心矛盾:SSE的异步特性与Spring Security默认拦截规则的生命周期冲突,非用户真实权限不足问题。

2. 解决方案:精准排除异步请求的权限校验

核心思路:通过.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() 设置保留初始请求的权限校验,让Spring Security忽略ASYNC类型的异步转发请求,兼顾安全性与功能正确性。

2.1 安全配置实现

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                // CSRF禁用,因为不使用session
                .csrf(AbstractHttpConfigurer::disable)
                // 禁用HTTP响应标头
                .headers(header -> header.cacheControl(HeadersConfigurer.CacheControlConfig::disable).frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
                // 认证失败处理类
                .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
                // 基于token,所以不需要session
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 注解标记允许匿名访问的url
                .authorizeHttpRequests((requests) -> {
                    permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
                    // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                    requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
                            .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() //新增设置*
                            // 静态资源,可匿名访问
                            .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
                            .requestMatchers("/webjars/**", "/druid/**").permitAll()
                            // 除上面外的所有请求全部需要鉴权认证
                            .anyRequest().authenticated();
                })
                // 添加Logout filter
                .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
                // 添加JWT filter
                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                // 添加CORS filter
                .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
                .addFilterBefore(corsFilter, LogoutFilter.class).build();
    }
}

2.2 其他情况

    也可以使用`.requestMatchers("/test").permitAll()`开放SSE接口!但是该方式会放弃网关层面的权限控制,需在业务代码中手动校验身份,极易引发安全问题。

二、异步线程池警告的解决:生产级配置

1. 警告原因:默认执行器的资源隐患

Spring MVC处理SSE等异步请求时,默认使用SimpleAsyncTaskExecutor,其核心缺陷是无线程复用机制——每次请求都会创建新线程。在高并发场景下,大量线程的创建与销毁会耗尽CPU和内存资源,因此Spring会抛出明确的生产环境使用警告。

2. 解决方案:自定义线程池实现线程复用

通过配置ThreadPoolTaskExecutor(基于线程池的执行器),并通过WebMvcConfigurer将其绑定到Spring MVC的异步支持,实现线程复用与资源管控。

2.1 线程池配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class AsyncWebMvcConfig implements WebMvcConfigurer {
    /**
     * 配置Spring MVC异步请求的任务执行器
     */
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setTaskExecutor(mvcAsyncTaskExecutor()); // 绑定自定义线程池
        configurer.setDefaultTimeout(60_000L); // 可选:SSE会话超时时间(毫秒),按需调整
    }
    /**
     * 定义生产级线程池Bean
     */
    @Bean(name = "mvcAsyncTaskExecutor")
    public Executor mvcAsyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int cpuCores = Runtime.getRuntime().availableProcessors(); // 动态获取CPU核心数
        // 核心参数配置
        executor.setCorePoolSize(cpuCores * 2); // 核心线程数:IO密集型任务推荐CPU核心数*2
        executor.setMaxPoolSize(cpuCores * 5);  // 最大线程数:高峰期弹性扩容上限
        executor.setQueueCapacity(100);         // 任务队列:缓冲瞬时突发流量
        executor.setThreadNamePrefix("sse-async-"); // 线程名前缀:便于日志追踪和监控
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略
        executor.setKeepAliveSeconds(60); // 空闲线程存活时间:超过核心线程数的空闲线程60秒后销毁
        executor.initialize(); // 初始化执行器
        return executor;
    }
}

2.2 服务层使用自定义线程池

若在服务层使用@Async注解实现异步逻辑,需指定自定义线程池的Bean名称,避免使用默认执行器:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class SseAsyncService {
    // 明确指定使用自定义线程池
    @Async("mvcAsyncTaskExecutor")
    public void handleSseMessage(String message) {
        // SSE相关的异步业务逻辑
        System.out.println("处理SSE消息:" + message);
    }
}

三、线程池核心参数的科学估算与调优

线程池的核心参数(corePoolSize、maxPoolSize、queueCapacity)直接影响接口性能,需结合任务类型(CPU密集/IO密集)和系统资源科学配置,以下为可落地的估算方法与调优思路。

1. 核心参数估算逻辑

参数名称估算依据参考公式/建议值
corePoolSize(核心线程数)任务类型决定CPU利用率,避免线程切换损耗CPU密集型:CPU核心数 + 1IO密集型(如SSE):CPU核心数 * 2
maxPoolSize(最大线程数)系统高峰期的弹性处理能力,避免资源过载公式:(CPU核心数 * 目标CPU使用率) / (1 - 阻塞系数)经验值:核心线程数的2-5倍(SSE推荐3-5倍)
queueCapacity(队列容量)缓冲瞬时流量,平衡响应延迟与任务堆积风险经验值:100-500(过大易导致延迟,过小易触发拒绝策略)

关键概念:阻塞系数指任务执行中IO等待(如网络请求、数据库操作)的时间占比,SSE接口为典型IO密集型任务,阻塞系数约0.8-0.9。

2. 生产环境调优步骤

  • 基准测试:低负载下运行接口,验证功能正常且线程池无异常;
  • 压力测试:使用JMeter、Gatling等工具模拟高并发,逐步提升并发用户数(如从100到1000);
  • 核心监控指标:聚焦以下指标判断配置合理性:
  • 系统资源:CPU使用率(建议稳定在60%-80%)、内存占用(无持续增长);
  • 线程池状态:活跃线程数(是否频繁接近maxPoolSize)、队列任务数(是否持续高位)、任务拒绝次数(需为0);
  • 接口性能:平均响应时间(无明显波动)、错误率(低于0.1%)。
  • 参数调整策略
  • CPU瓶颈早现但吞吐量低:核心线程数过高,适当下调;
  • 活跃线程达上限且队列满:适度提升maxPoolSize和queueCapacity;
  • 任务频繁被拒绝:需从服务扩容(增加CPU/内存)或业务优化(拆分任务)层面解决。

3. 拒绝策略选择

当线程池和队列均满时,拒绝策略决定新任务的处理方式,SSE接口推荐优先级如下:

  • CallerRunsPolicy(推荐):由提交任务的线程(如Tomcat工作线程)直接执行任务,产生自然背压,减缓请求提交速度,避免任务丢失;
  • DiscardOldestPolicy:仅当任务允许丢弃时使用,丢弃队列中最旧的任务后接收新任务;
  • AbortPolicy(默认):直接抛出RejectedExecutionException,需结合业务捕获处理,否则会导致接口报错。

四、核心结论与实践建议

  • SSE接口的AccessDeniedException:核心解决手段是通过NegatedRequestMatcher排除ASYNC类型请求的权限校验,而非开放接口权限;
  • 异步线程池警告:必须自定义ThreadPoolTaskExecutor实现线程复用,SimpleAsyncTaskExecutor严禁用于生产环境;
  • 参数配置核心:以CPU核心数为基准,区分任务类型(SSE为IO密集型),结合压测与监控动态调优;
  • 监控优先:为线程池设置清晰的线程名前缀,便于通过APM工具(如SkyWalking)监控线程状态,快速定位问题。

(注:文档部分内容可能由 AI 生成)

到此这篇关于JAVA SSE接口开发中的Spring Security与异步线程池配置的文章就介绍到这了,更多相关Spring Security与异步线程池配置内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解 Java中日期数据类型的处理之格式转换的实例

    详解 Java中日期数据类型的处理之格式转换的实例

    这篇文章主要介绍了详解 Java中日期数据类型的处理之格式转换的实例的相关资料,日期以及时间格式处理,在Java中时间格式一般会涉及到的数据类型包括Calendar类和Date类,需要的朋友可以参考下
    2017-08-08
  • Java中的重要核心知识点之继承详解

    Java中的重要核心知识点之继承详解

    继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为
    2021-10-10
  • SpringBoot如何实现Tomcat自动配置

    SpringBoot如何实现Tomcat自动配置

    这篇文章主要介绍了SpringBoot如何实现Tomcat自动配置,帮助大家更好的理解和学习使用SpringBoot框架,感兴趣的朋友可以了解下
    2021-03-03
  • 用Java程序判断是否是闰年的简单实例

    用Java程序判断是否是闰年的简单实例

    下面小编就为大家带来一篇用Java程序判断是否是闰年的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • java 启动exe程序,传递参数和获取参数操作

    java 启动exe程序,传递参数和获取参数操作

    这篇文章主要介绍了java 启动exe程序,传递参数和获取参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • 关于stream().sorted()以及java中常用的比较器排序

    关于stream().sorted()以及java中常用的比较器排序

    这篇文章主要介绍了关于stream().sorted()以及java中常用的比较器排序,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • java 实现简单圣诞树的示例代码(圣诞节快乐)

    java 实现简单圣诞树的示例代码(圣诞节快乐)

    这篇文章主要介绍了java 实现简单圣诞树的示例代码(圣诞节快乐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • Spring事务&Spring整合MyBatis的两种方式

    Spring事务&Spring整合MyBatis的两种方式

    这篇文章主要介绍了Spring事务&Spring整合MyBatis的两种方式,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-02-02
  • Java实战之图书管理系统的实现

    Java实战之图书管理系统的实现

    这篇文章主要介绍了如何利用Java语言编写一个图书管理系统,文中采用的技术有Springboot、SpringMVC、MyBatis、ThymeLeaf 等,需要的可以参考一下
    2022-03-03
  • java固定大小队列的几种实现方式详解

    java固定大小队列的几种实现方式详解

    队列的特点是节点的排队次序和出队次序按入队时间先后确定,即先入队者先出队,后入队者后出队,这篇文章主要给大家介绍了关于java固定大小队列的几种实现方式,需要的朋友可以参考下
    2021-07-07

最新评论