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

C#解析含DateTimeOffset的JSON时哈希验证失败求助

解决Webhook验证中DateTime序列化时区格式不匹配问题

问题背景

接收第三方Webhook请求时,JSON中的DateTime字段采用2023-04-05T14:13:47+0200格式(时区部分无冒号)。使用C# Newtonsoft.Json反序列化后再重新序列化时,时区部分会自动变为+02:00(添加冒号),导致SHA256哈希计算结果与第三方提供的validation_hash不匹配,无法通过请求验证。当前仅能手动修改字段格式,需更优解决方案。

原始请求JSON示例

{
    "message": "OK",
    "code": 200,
    "current_time": "2023-04-05T14:13:50+0200",
    "order": {
                "uuid": "45B32B3C-B01A-4D82-A03B-D5D9709DC92C",
                "created": "2023-04-05T14:13:47+0200",
                "created_from_client_timezone": "2023-04-05T14:13:47+0200",
                "amount": 1000,
                "currency": "978",
                "paid": false, 
                "status": "CREATED",
                "safe": true,
                "refunded": 0,
                "additional": null,
                "service": "REDSYSPSD2",
                "service_uuid": "F9D76AE8-DDBB-4F17-8074-C2DABEE0A919",
                "customer": "test2222",
                "cof_txnid": null,
                "transactions": [],
                "token": "345e77cc5a2616030ef5965d5513e1088adbd087b637da99eefc80f0729c943a26ef5e3a45b454819c62015e3d74f3282ecd2e1ab0b99a34de46d8aa37106a3d",
                "ip": null,
                "urls": { 
                            "payment_card":"http:\/\/ws.paylands.loc\/su\/QY6cnT3MasUB",
                            "bizum":"http:\/\/ws.paylands.loc\/su\/0SWxTAvN0QkJ",
                            "3ds_tokenized":"http:\/\/ws.paylands.loc\/su\/3wEDqxIhjjsD"
                        },
                "reference": null,
                "dynamic_descriptor": "\u00eb\u00f1a,\u00e1ds,\u00f4 \u00f2,",
                "threeds_data": null
           },
    "client": {
                  "uuid":"42B8CF56-A7D7-4D4A-8349-4E27263CB2D5"
              },
    "validation_hash": "414c8190e6b15ac767ff3217e994ef8687368ec8ebe6ab9ad570799d2f746cb7"
}

原哈希计算代码

var jsonDe = JsonConvert.DeserializeObject<dynamic>(requestBody);

var signatureData = JsonConvert.SerializeObject(new Dictionary<string, object>
{
    { "order", jsonDe.order },
    { "client", jsonDe.client },
});

var signature = CalculateSHA256Hash(signatureData + settings.Signature);

if (signature != dto.ValidationHash)
{
    serviceResult.AddError(0, _localizationRepository.Get("The request is invalid."));
    return serviceResult;
}

static string CalculateSHA256Hash(string input)
{
    using (SHA256 sha256Hash = SHA256.Create())
    {
        byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(input));

        StringBuilder builder = new StringBuilder();

        for (int i = 0; i < bytes.Length; i++)
        {
            builder.Append(bytes[i].ToString("x2"));
        }

        return builder.ToString();
    }
}

解决方案

方案1:直接操作原始JSON字符串提取字段(推荐)

绕过反序列化DateTime的步骤,直接从原始JSON中提取order和client节点的字符串片段,拼接后计算哈希,完全保留原始时间格式。

var rawJObject = JObject.Parse(requestBody);
// 直接拼接order和client的原始JSON字符串
var signatureData = $"{{\"order\":{rawJObject["order"].ToString()},\"client\":{rawJObject["client"].ToString()}}}";
var signature = CalculateSHA256Hash(signatureData + settings.Signature);

优势:无需处理任何类型转换,完全复用第三方原始数据格式,从根源避免序列化导致的格式变更,性能最优。

方案2:自定义DateTime转换器强制格式

如果需要将JSON映射为强类型对象,可自定义JsonConverter,让DateTime/DateTimeOffset序列化时输出无冒号的时区格式。

自定义转换器代码

public class CustomDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
    // 匹配第三方的时间格式
    private const string TimeFormat = "yyyy-MM-ddTHH:mm:sszzzz";

    public override void WriteJson(JsonWriter writer, DateTimeOffset value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString(TimeFormat));
    }

    public override DateTimeOffset ReadJson(JsonReader reader, Type objectType, DateTimeOffset existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        return DateTimeOffset.Parse(reader.Value.ToString());
    }
}

// 若使用DateTime类型,需对应实现DateTime的转换器
public class CustomDateTimeConverter : JsonConverter<DateTime>
{
    private const string TimeFormat = "yyyy-MM-ddTHH:mm:sszzzz";

    public override void WriteJson(JsonWriter writer, DateTime value, JsonSerializer serializer)
    {
        writer.WriteValue(value.ToString(TimeFormat));
    }

    public override DateTime ReadJson(JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        return DateTime.Parse(reader.Value.ToString());
    }
}

序列化时应用转换器

var jsonDe = JsonConvert.DeserializeObject<dynamic>(requestBody);
var signatureData = JsonConvert.SerializeObject(new Dictionary<string, object>
{
    { "order", jsonDe.order },
    { "client", jsonDe.client },
}, new JsonSerializerSettings
{
    Converters = new List<JsonConverter> 
    { 
        new CustomDateTimeOffsetConverter(),
        new CustomDateTimeConverter() // 若有DateTime类型字段需添加
    }
});
var signature = CalculateSHA256Hash(signatureData + settings.Signature);

优势:适配强类型对象场景,确保序列化输出格式符合要求。

方案3:保留JToken原始类型序列化

使用JObject解析后,通过DeepClone保留节点的原始类型(时间字段以字符串形式存储而非DateTime),再序列化时输出原始格式。

var rawJObject = JObject.Parse(requestBody);
var signatureObject = new JObject
{
    ["order"] = rawJObject["order"].DeepClone(),
    ["client"] = rawJObject["client"].DeepClone()
};
// 无格式缩进确保和第三方JSON结构一致
var signatureData = signatureObject.ToString(Formatting.None);
var signature = CalculateSHA256Hash(signatureData + settings.Signature);

优势:既可以操作JSON节点,又能保留原始格式,适合需要对节点做简单修改的场景。


内容的提问来源于stack exchange,提问作者kamal hamidi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 13:24:57