Spring Boot 401未授权及SPRING_SESSION_ATTRIBUTES重复插入问题求助
Spring Session并发访问导致401未授权与重复插入异常排查
环境信息
- 后端:Spring Boot 3.1.1(从3.0.x版本升级),采用Spring Session JDBC存储会话
- 前端:Angular
问题现象
- 特定页面随机出现会话断开,后端返回401未授权状态码
- 当两个AJAX请求并发发送到后端时,偶尔触发
SPRING_SESSION_ATTRIBUTES表重复插入异常,抛出DuplicateKeyException
相关日志
401错误相关Debug日志
2023-08-08 14:30:44,549 DEBUG o.s.s.w.h.SessionRepositoryFilter$SessionRepositoryRequestWrapper [http-nio-8080-exec-47] No session found by id: Caching result for getSession(false) for this HttpServletRequest. 2023-08-08 14:30:44,550 DEBUG o.s.w.s.h.AbstractHandlerMapping [http-nio-8080-exec-47] Mapped to com.ubipharm.cataloguepara.controller.back.SheetController#get(String, String, HttpSession) 2023-08-08 14:30:44,550 DEBUG o.s.s.w.h.SessionRepositoryFilter$SessionRepositoryRequestWrapper [http-nio-8080-exec-47] No session found by id: Caching result for getSession(false) for this HttpServletRequest. 2023-08-08 14:30:44,551 DEBUG o.s.s.w.h.SessionRepositoryFilter$SessionRepositoryRequestWrapper [http-nio-8080-exec-47] A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for org.springframework.session.web.http.SessionRepositoryFilter.SESSION_LOGGER java.lang.RuntimeException: For debugging purposes only (not an error) at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:313) at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:327) at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:194) at jakarta.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:229) at jakarta.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:229) at jakarta.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:229) at org.springframework.web.util.WebUtils.setSessionAttribute(WebUtils.java:409) at org.springframework.web.servlet.i18n.SessionLocaleResolver.setLocaleContext(SessionLocaleResolver.java:193) at org.springframework.web.servlet.LocaleContextResolver.setLocale(LocaleContextResolver.java:110) at org.springframework.web.servlet.i18n.LocaleChangeInterceptor.preHandle(LocaleChangeInterceptor.java:125) .... 2023-08-08 14:30:44,564 DEBUG o.s.o.j.s.OpenEntityManagerInViewInterceptor [http-nio-8080-exec-46] Opening JPA EntityManager in OpenEntityManagerInViewInterceptor 2023-08-08 14:30:44,565 DEBUG o.s.w.s.m.m.a.AbstractMessageConverterMethodProcessor [http-nio-8080-exec-46] Using 'application/json', given [application/json, text/plain, */*] and supported [application/json, application/*+json] 2023-08-08 14:30:44,566 DEBUG o.s.w.s.m.m.a.AbstractMessageConverterMethodProcessor [http-nio-8080-exec-46] Nothing to write: null body 2023-08-08 14:30:44,566 DEBUG o.s.o.j.s.OpenEntityManagerInViewInterceptor [http-nio-8080-exec-46] Closing JPA EntityManager in OpenEntityManagerInViewInterceptor 2023-08-08 14:30:44,566 DEBUG o.s.w.s.FrameworkServlet [http-nio-8080-exec-46] **Completed 401 UNAUTHORIZED**
重复插入异常日志
2023-08-08 15:23:54,710 DEBUG o.s.t.s.TransactionTemplate [http-nio-8080-exec-62] Initiating transaction rollback on application exception org.springframework.dao.DuplicateKeyException: PreparedStatementCallback; SQL [INSERT INTO SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) VALUES (?, ?, ?) ]; (conn=33870) Duplicate entry '3f974714-8f57-4358-a609-bd3c6c2b7156-org.springframework.web....' for key 'PRIMARY' at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:251) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70) at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1538) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:667) at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:960) at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:1015) at org.springframework.session.jdbc.JdbcIndexedSessionRepository.insertSessionAttributes(JdbcIndexedSessionRepository.java:561) at org.springframework.session.jdbc.JdbcIndexedSessionRepository$JdbcSession.lambda$save$11(JdbcIndexedSessionRepository.java:912) at org.springframework.transaction.support.TransactionOperations.lambda$executeWithoutResult$0(TransactionOperations.java:68) at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140) at org.springframework.transaction.support.TransactionOperations.executeWithoutResult(TransactionOperations.java:67)
已做排查
- 已将Spring Boot版本从3.0.2升级至3.1.1,问题未解决
- 确认问题由并发AJAX请求触发,推测核心原因是Spring Session并发访问冲突
解决思路
1. 修复LocaleResolver的会话创建逻辑
从日志可见,SessionLocaleInterceptor在请求预处理阶段调用getSession(true)强制创建新会话,并发场景下会导致会话状态不一致:
- 替换
SessionLocaleResolver为CookieLocaleResolver,将语言信息存储到Cookie中,完全避免依赖会话 - 自定义LocaleResolver实现,在设置会话属性前检查会话是否存在,仅在必要时创建会话;或添加同步锁,确保同一会话的属性操作串行执行
2. 优化Spring Session JDBC的并发写入
重复插入异常源于JdbcIndexedSessionRepository并发插入相同主键的会话属性:
- 覆盖
JdbcIndexedSessionRepository的insertSessionAttributes方法,使用数据库的UPSERT语法(MySQL用INSERT ... ON DUPLICATE KEY UPDATE,PostgreSQL用INSERT ... ON CONFLICT DO UPDATE),避免主键冲突报错 - 调整Spring Session配置:设置
spring.session.jdbc.flush-mode=on-save,仅在会话保存时刷新属性,减少不必要的数据库写入 - 为会话操作添加分布式锁,基于会话ID锁定,确保同一会话的属性修改操作串行执行
3. 前端请求优化
- 对特定页面的并发AJAX请求做队列化处理,避免同时发送多个请求触发会话冲突
- 检查Angular的HTTP拦截器,确保请求正确携带会话Cookie,防止请求丢失会话ID导致新会话创建
4. 排查会话认证失效问题
401未授权可能是新会话创建后原认证信息丢失:
- 检查Spring Security的认证逻辑,确认认证信息(如Authentication对象)是否正确绑定到会话
- 验证会话超时配置(
server.servlet.session.timeout),排除会话提前过期的可能 - 开启Spring Session的DEBUG日志,跟踪会话ID的变化,确认请求是否在不同会话间切换
内容的提问来源于stack exchange,提问作者stephlx
相关产品推荐
相关产品推荐

