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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 22:13:11