fix(security): 修复RedisTokenManager会话反序列化类型转换异常

- 提取JsonMapper为共享Bean,统一序列化配置
- 使用convertValue替代直接强转,正确处理嵌套泛型
This commit is contained in:
Ray.Hao
2026-07-03 18:22:48 +08:00
parent 41365338a4
commit 12b226fdc9
2 changed files with 29 additions and 10 deletions

View File

@@ -24,10 +24,12 @@ public class RedisConfig {
* 修改 Redis 序列化方式,默认 JdkSerializationRedisSerializer * 修改 Redis 序列化方式,默认 JdkSerializationRedisSerializer
* *
* @param redisConnectionFactory {@link RedisConnectionFactory} * @param redisConnectionFactory {@link RedisConnectionFactory}
* @param jsonMapper Jackson 序列化器(统一使用不写入类型信息的配置)
* @return {@link RedisTemplate} * @return {@link RedisTemplate}
*/ */
@Bean @Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory,
JsonMapper jsonMapper) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setConnectionFactory(redisConnectionFactory);
@@ -37,10 +39,6 @@ public class RedisConfig {
redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setHashKeySerializer(RedisSerializer.string());
// Value 使用自定义 JSON 序列化(不写入类型信息,避免 HashSet 等集合被序列化成带 @class 的结构) // Value 使用自定义 JSON 序列化(不写入类型信息,避免 HashSet 等集合被序列化成带 @class 的结构)
JsonMapper jsonMapper = JsonMapper.builder()
.disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
JacksonJsonRedisSerializer<Object> jsonSerializer = new JacksonJsonRedisSerializer<>(jsonMapper, Object.class); JacksonJsonRedisSerializer<Object> jsonSerializer = new JacksonJsonRedisSerializer<>(jsonMapper, Object.class);
redisTemplate.setValueSerializer(jsonSerializer); redisTemplate.setValueSerializer(jsonSerializer);
@@ -50,4 +48,17 @@ public class RedisConfig {
return redisTemplate; return redisTemplate;
} }
/**
* 统一的 JsonMapper Bean
* <p>
* 禁止将日期序列化为时间戳;不写入类型信息,保证 Redis 存储的 JSON 纯净可读。
* 需要反序列化到特定类型时,调用方应使用 {@link JsonMapper#convertValue(Object, Class)} 显式转换。
*/
@Bean
public JsonMapper jsonMapper() {
return JsonMapper.builder()
.disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
}
} }

View File

@@ -19,6 +19,8 @@ import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import tools.jackson.databind.json.JsonMapper;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -46,10 +48,14 @@ public class RedisTokenManager implements TokenManager {
private final SecurityProperties securityProperties; private final SecurityProperties securityProperties;
private final RedisTemplate<String, Object> redisTemplate; private final RedisTemplate<String, Object> redisTemplate;
private final JsonMapper jsonMapper;
public RedisTokenManager(SecurityProperties securityProperties, RedisTemplate<String, Object> redisTemplate) { public RedisTokenManager(SecurityProperties securityProperties,
RedisTemplate<String, Object> redisTemplate,
JsonMapper jsonMapper) {
this.securityProperties = securityProperties; this.securityProperties = securityProperties;
this.redisTemplate = redisTemplate; this.redisTemplate = redisTemplate;
this.jsonMapper = jsonMapper;
} }
/** /**
@@ -96,8 +102,9 @@ public class RedisTokenManager implements TokenManager {
*/ */
@Override @Override
public Authentication parseToken(String token) { public Authentication parseToken(String token) {
UserSession userSession = (UserSession) redisTemplate.opsForValue().get(formatTokenKey(token)); Object raw = redisTemplate.opsForValue().get(formatTokenKey(token));
if (userSession == null) return null; if (raw == null) return null;
UserSession userSession = jsonMapper.convertValue(raw, UserSession.class);
// 构建用户权限集合 // 构建用户权限集合
Set<SimpleGrantedAuthority> authorities = null; Set<SimpleGrantedAuthority> authorities = null;
@@ -144,11 +151,12 @@ public class RedisTokenManager implements TokenManager {
*/ */
@Override @Override
public AuthenticationToken refreshToken(String refreshToken) { public AuthenticationToken refreshToken(String refreshToken) {
UserSession userSession = (UserSession) redisTemplate.opsForValue() Object raw = redisTemplate.opsForValue()
.get(StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken)); .get(StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken));
if (userSession == null) { if (raw == null) {
throw new TokenInvalidException(ResultCode.REFRESH_TOKEN_INVALID); throw new TokenInvalidException(ResultCode.REFRESH_TOKEN_INVALID);
} }
UserSession userSession = jsonMapper.convertValue(raw, UserSession.class);
Object oldAccessTokenValue = redisTemplate.opsForValue().get(StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, userSession.getUserId())); Object oldAccessTokenValue = redisTemplate.opsForValue().get(StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, userSession.getUserId()));
// 删除旧的访问令牌记录 // 删除旧的访问令牌记录
Optional.of(oldAccessTokenValue) Optional.of(oldAccessTokenValue)