Spring Session Redis序列化OAuth2AuthenticationToken遇异常求助
序列化扁平化问题原因
你给CustomUserOidcPrincipal里的字段加了@JsonIgnore,但Jackson序列化时会通过OidcUser接口的getter方法(比如getUserInfo())访问到OidcUserInfo对象,进而把里面的属性扁平化输出到principal节点下。@JsonIgnore只作用于当前字段,无法阻止Jackson遍历关联对象的属性。
反序列化报错原因
OidcIdToken、OidcUserInfo这类Spring Security OAuth2的类都是不可变类,没有公开的setter方法,Jackson默认反序列化策略需要setter赋值,所以遇到audience这类属性时会抛出SerializationException。
具体解决步骤
1. 解决序列化扁平化问题
给CustomUserOidcPrincipal中返回OidcUserInfo、OidcIdToken的getter方法也加上@JsonIgnore,同时配置Jackson禁用自动检测getter,避免自动展开关联对象:
public class CustomUserOidcPrincipal implements OidcUser { // 自定义字段 private String userId; // 标记忽略的字段 @JsonIgnore private OidcUserInfo userInfo; @JsonIgnore private OidcIdToken idToken; // 重写OidcUser接口的getter,添加@JsonIgnore @Override @JsonIgnore public OidcUserInfo getUserInfo() { return userInfo; } @Override @JsonIgnore public OidcIdToken getIdToken() { return idToken; } // 其他必要方法实现... }
在Redis序列化配置中调整ObjectMapper:
@Configuration public class RedisSessionConfig { @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { ObjectMapper objectMapper = new ObjectMapper(); // 禁用自动检测getter,只序列化显式标注的字段 objectMapper.configure(MapperFeature.AUTO_DETECT_GETTERS, false); // 注册Jackson模块支持JDK8和时间类型 objectMapper.registerModule(new JavaTimeModule()); objectMapper.registerModule(new Jdk8Module()); return new GenericJackson2JsonRedisSerializer(objectMapper); } }
2. 解决反序列化无setter问题
通过Jackson Mixin给Spring Security的Oidc相关类添加构造方法注解,让Jackson可以通过构造方法反序列化这些不可变类:
先创建Mixin类:
// OidcIdToken的Mixin public abstract class OidcIdTokenMixin { @JsonCreator public OidcIdTokenMixin(@JsonProperty("iss") String issuer, @JsonProperty("sub") String subject, @JsonProperty("aud") List<String> audience, @JsonProperty("exp") Instant expiresAt, @JsonProperty("iat") Instant issuedAt, @JsonProperty("nonce") String nonce, @JsonProperty("claims") Map<String, Object> claims) { } } // OidcUserInfo的Mixin public abstract class OidcUserInfoMixin { @JsonCreator public OidcUserInfoMixin(@JsonProperty("claims") Map<String, Object> claims) { } }
然后在Redis序列化配置中注册Mixin:
@Configuration public class RedisSessionConfig { @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { ObjectMapper objectMapper = new ObjectMapper(); // 注册Mixin支持不可变类反序列化 objectMapper.addMixIn(OidcIdToken.class, OidcIdTokenMixin.class); objectMapper.addMixIn(OidcUserInfo.class, OidcUserInfoMixin.class); // 注册Jackson模块 objectMapper.registerModule(new JavaTimeModule()); objectMapper.registerModule(new Jdk8Module()); // 忽略未知属性,避免其他未处理字段报错 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return new GenericJackson2JsonRedisSerializer(objectMapper); } }
3. 额外优化:自定义序列化器(可选)
如果上述方法仍有问题,可以给CustomUserOidcPrincipal编写自定义序列化器/反序列化器,完全控制序列化逻辑:
// 序列化器 public class CustomUserOidcPrincipalSerializer extends StdSerializer<CustomUserOidcPrincipal> { public CustomUserOidcPrincipalSerializer() { super(CustomUserOidcPrincipal.class); } @Override public void serialize(CustomUserOidcPrincipal value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(); // 只序列化需要的自定义字段和核心claims gen.writeStringField("userId", value.getUserId()); gen.writeObjectField("claims", value.getClaims()); gen.writeEndObject(); } } // 反序列化器 public class CustomUserOidcPrincipalDeserializer extends StdDeserializer<CustomUserOidcPrincipal> { public CustomUserOidcPrincipalDeserializer() { super(CustomUserOidcPrincipal.class); } @Override public CustomUserOidcPrincipal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.getCodec().readTree(p); String userId = node.get("userId").asText(); Map<String, Object> claims = p.getCodec().treeToValue(node.get("claims"), Map.class); // 重新构建Oidc相关对象 OidcUserInfo userInfo = new OidcUserInfo(claims); OidcIdToken idToken = OidcIdToken.withTokenValue("placeholder") .claims(claims) .build(); return new CustomUserOidcPrincipal(userId, userInfo, idToken); } }
在CustomUserOidcPrincipal上标注使用自定义序列化器:
@JsonSerialize(using = CustomUserOidcPrincipalSerializer.class) @JsonDeserialize(using = CustomUserOidcPrincipalDeserializer.class) public class CustomUserOidcPrincipal implements OidcUser { // 类内容不变 }
内容的提问来源于stack exchange,提问作者Allen Bastian

