我正在尝试将服务与Zoho订阅集成在一起,并且我想确保呼叫实际上来自Zoho。为此,在Webhook中,勾选“我想保护此Webhook”,并遵循其linked page上的文档-但我很难生成匹配的哈希值。正确验证哈希的技巧是什么?
我设法破解了,这是我在此过程中发现的一些陷阱,以及有效的.Net实现。
Gotchas
配置Webhook时
代码
我们的验证被实现为ASP-Net筛选器,我删除了该位,以专注于哈希计算位。
public async Task ValidateZohoCall(HttpRequest request)
{
var zohoCalculatedHashValue = request.Headers.GetHeaderValue("X-Zoho-Webhook-Signature");
if (string.IsNullOrEmpty(zohoCalculatedHashValue))
{
throw new Exception("Webhook signature is missing.");
}
else
{
var toHash = BuildZohoStringToHash(request);
string locallyCalculatedHashValue = GetHash(toHash);
// Compare our value against what is in the request headers
if (locallyCalculatedHashValue != zohoCalculatedHashValue)
throw new Exception("Webhook signature is invalid.");
}
}
public string GetRequestBody(HttpRequest request)
{
string requestBody = "";
request.EnableRewind();
using (var stream = new StreamReader(request.Body))
{
stream.BaseStream.Position = 0;
requestBody = stream.ReadToEnd();
}
return requestBody;
}
/// <summary>
/// Concatenates parts of the http request into a single string according to
/// Zoho specifications.
/// </summary>
public string BuildZohoStringToHash(HttpRequest request)
{
StringBuilder sb = new StringBuilder();
// Get request fields from query string and form content.
var mergedRequestFields = new Dictionary<string, object>();
mergedRequestFields.Add(GetItemsFromQuery(request));
mergedRequestFields.Add(GetItemsFromForm(request));
// Sort those fields alphabetically by key name and append to output string.
foreach (var kv in mergedRequestFields.OrderBy(x =>
x.Key).ToDictionary(x => x.Key, y => y.Value))
sb.Append($"{kv.Key}{kv.Value}");
// Default-payload and raw type messages should not be processed,
// just appended to the end of the string.
sb.Append(GetRequestBody(request));
return sb.ToString();
}
public Dictionary<string, object> GetItemsFromQuery(HttpRequest request)
{
return request.Query.ToDictionary(x => x.Key, y => (object)y.Value);
}
public Dictionary<string, object> GetItemsFromForm(HttpRequest request)
{
if (!request.HasFormContentType || (request.Form == null) || !request.Form.Any())
return new Dictionary<string, object>();
return request.Form.ToDictionary(x => x.Key, y => (object)y.Value);
}
public string GetHash(string text)
{
var encoding = new UTF8Encoding();
byte[] textBytes = encoding.GetBytes(text);
byte[] keyBytes = encoding.GetBytes(_zohoWebhookSecret);
byte[] hashBytes;
using (HMACSHA256 hash = new HMACSHA256(keyBytes))
hashBytes = hash.ComputeHash(textBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}