LICENSE_SIGNATURE_ISSUE_SOLUTION.md 4.4 KB

License签名验证失败问题分析与解决方案

问题描述

在项目中出现License签名验证失败的问题,根本原因是YudaoJacksonAutoConfiguration中配置的全局LocalDateTime序列化器与LicenseGenerator中的序列化配置不一致,导致时间字段在签名数据生成时格式不同。

问题根本原因

1. YudaoJacksonAutoConfiguration配置

YudaoJacksonAutoConfiguration中配置了全局的LocalDateTime序列化器:

// 新增 LocalDateTime 序列化、反序列化规则,使用 Long 时间戳
.addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);

TimestampLocalDateTimeSerializerLocalDateTime序列化为时间戳:

@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    // 将 LocalDateTime 对象,转换为 Long 时间戳
    gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
}

2. LicenseGenerator配置

LicenseGenerator使用标准的Jackson配置:

public LicenseGenerator() {
    this.objectMapper = new ObjectMapper();
    this.objectMapper.registerModule(new JavaTimeModule());
    this.objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 禁用时间戳格式
    this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
}

3. 签名数据生成差异

  • LicenseGenerator生成签名时:使用标准格式(如:2024-01-15T10:30:00
  • 项目验证签名时:由于全局配置,LocalDateTime被序列化为时间戳(如:1705294200000

这导致签名数据字符串不同,进而导致签名验证失败。

解决方案

方案1:修改LicenseGenerator使用项目的序列化配置(推荐)

修改LicenseGenerator的构造函数,使其与项目保持一致:

public LicenseGenerator() {
    this.objectMapper = new ObjectMapper();
    this.objectMapper.registerModule(new JavaTimeModule());
    
    // 使用与项目一致的LocalDateTime序列化配置
    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE);
    simpleModule.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
    this.objectMapper.registerModule(simpleModule);
    
    this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
}

方案2:在CryptoUtil中使用独立的ObjectMapper

为License验证创建独立的ObjectMapper,不受全局配置影响:

private static ObjectMapper createLicenseObjectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    return mapper;
}

方案3:修改签名数据生成逻辑

createSignatureData方法中,对时间字段进行特殊处理,确保格式一致:

private void appendField(StringBuilder sb, String fieldName, Object value) {
    sb.append(fieldName).append("=");
    if (value != null) {
        if (value instanceof LocalDateTime) {
            // 统一使用时间戳格式
            LocalDateTime dateTime = (LocalDateTime) value;
            long timestamp = dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
            sb.append(timestamp);
        } else {
            sb.append(value.toString());
        }
    }
    sb.append(";");
}

推荐实施步骤

  1. 立即解决:采用方案1,修改LicenseGenerator使其与项目配置保持一致
  2. 重新生成License文件:使用修改后的LicenseGenerator重新生成所有License文件
  3. 测试验证:确保新生成的License文件能够正常验证
  4. 文档更新:更新相关文档,说明时间序列化的统一标准

注意事项

  1. 修改后需要重新生成所有现有的License文件
  2. 确保开发、测试、生产环境使用相同的序列化配置
  3. 建议在License生成和验证过程中添加详细的日志,便于问题排查
  4. 考虑添加单元测试,验证签名生成和验证的一致性

预防措施

  1. 建立License生成和验证的集成测试
  2. 在CI/CD流程中加入License兼容性检查
  3. 统一项目中所有时间序列化的配置标准
  4. 定期检查Jackson配置的变更对License系统的影响