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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 18:15:55