使用@EnableJdbcHttpSession时PRINCIPAL_NAME为空及用户主体丢失问题
嘿,我来帮你拆解下这个问题——这其实是在传统非Spring Boot的Spring应用里,整合Spring Session JDBC和Spring Security时很容易踩的一个坑。核心问题是启用@EnableJdbcHttpSession后,Spring Security的认证信息没有被正确存储到数据库会话中,导致前端取不到、数据库PRINCIPAL_NAME字段为空。下面是具体的原因和解决办法:
1. 默认序列化器不支持Spring Security的Authentication对象
Spring Session JDBC默认使用JdkSerializationHttpSessionSerializer来序列化会话对象,但这个序列化器对Spring Security的Authentication(尤其是里面的Principal对象)支持不好,经常会导致序列化时丢失认证信息,自然数据库的PRINCIPAL_NAME字段也无法被填充。
解决办法:改用Jackson序列化器并注册Spring Security模块
- 首先在
pom.xml中添加Spring Security的Jackson支持依赖:<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jackson2</artifactId> <version>和你的Spring Security版本保持一致</version> </dependency> - 然后创建自定义的Spring Session配置类,替换默认的序列化器:
这样配置后,Spring Session会用Jackson来序列化会话中的所有对象,包括Spring Security的认证信息,既能保留完整的@Configuration @EnableJdbcHttpSession public class CustomJdbcHttpSessionConfig extends JdbcHttpSessionConfiguration { @Override public void setHttpSessionSerializer(HttpSessionSerializer httpSessionSerializer) { ObjectMapper objectMapper = new ObjectMapper(); // 注册Spring Security的Jackson模块,让它能正确序列化认证对象 objectMapper.registerModule(new SpringSecurityJackson2Module()); // 开启默认类型信息,确保反序列化时能识别具体的Authentication类型 objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); super.setHttpSessionSerializer(new Jackson2HttpSessionSerializer(objectMapper)); } }Principal数据,也能让数据库的PRINCIPAL_NAME字段被正确填充。
2. 过滤器顺序配置错误
在非Spring Boot应用中,过滤器的执行顺序至关重要。如果Spring Security的过滤器在Spring Session的SessionRepositoryFilter之前执行,那么认证信息还没被存入会话,就被Spring Session的过滤器处理了,导致最终存储到数据库的会话里没有认证数据。
解决办法:确保SessionRepositoryFilter先于Spring Security过滤器执行
方式一:Java配置
如果用Java配置Web应用,在初始化类中指定过滤器顺序:
@Configuration public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Filter[] getServletFilters() { // 获取Spring Session的会话过滤器 SessionRepositoryFilter<?> sessionFilter = getApplicationContext().getBean(SessionRepositoryFilter.class); // 获取Spring Security的过滤器代理 DelegatingFilterProxy securityFilter = new DelegatingFilterProxy("springSecurityFilterChain"); // 顺序:Session过滤器在前,Security过滤器在后 return new Filter[]{sessionFilter, securityFilter}; } }
方式二:web.xml配置
如果用传统的web.xml配置,调整过滤器的定义顺序:
<!-- 先配置Spring Session的过滤器 --> <filter> <filter-name>springSessionRepositoryFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>ERROR</dispatcher> </filter-mapping> <!-- 再配置Spring Security的过滤器 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3. 数据库表结构不符合Spring Session要求
如果你的SPRING_SESSION表不是用Spring Session官方提供的脚本创建的,可能缺少PRINCIPAL_NAME字段或者字段类型不正确,导致无法存储主体信息。
解决办法:使用官方标准表结构
以MySQL为例,执行以下SQL创建正确的表结构:
CREATE TABLE SPRING_SESSION ( PRIMARY_ID CHAR(36) NOT NULL, SESSION_ID CHAR(36) NOT NULL, CREATION_TIME BIGINT NOT NULL, LAST_ACCESS_TIME BIGINT NOT NULL, MAX_INACTIVE_INTERVAL INT NOT NULL, EXPIRY_TIME BIGINT NOT NULL, PRINCIPAL_NAME VARCHAR(100), PRIMARY KEY (PRIMARY_ID), UNIQUE KEY SPRING_SESSION_IX1 (SESSION_ID), KEY SPRING_SESSION_IX2 (EXPIRY_TIME), KEY SPRING_SESSION_IX3 (PRINCIPAL_NAME) ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; CREATE TABLE SPRING_SESSION_ATTRIBUTES ( SESSION_PRIMARY_ID CHAR(36) NOT NULL, ATTRIBUTE_NAME VARCHAR(200) NOT NULL, ATTRIBUTE_BYTES BLOB NOT NULL, PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME), CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
按照这三个步骤逐一排查,应该就能解决你的问题了——既能让${sessionScope.SPRING_SECURITY_CONTEXT.authentication.principal}正常显示,也能让数据库的PRINCIPAL_NAME字段填充正确的值。
内容的提问来源于stack exchange,提问作者Todor27

