问题描述
我正在尝试将服务与Zoho订阅集成在一起,并且我想确保该呼叫实际上来自Zoho.为此,在Webhook中,勾选我想保护此Webhook",并按照其链接页面-但是我很难生成匹配的哈希值.正确验证哈希的技巧是什么?
I'm trying to integrate a service with Zoho subscriptions, and I want to make sure that the call actually comes from Zoho. To do that, in the webhook, I tick "I want to secure this webhook" and follow the documentation on their linked page - but I struggle to generate matching hash values.What are the tricks of correctly verifying the hash?
推荐答案
我设法破解了它,这是我在此过程中发现的一些陷阱,以及有效的.Net实现.
I managed to crack it, here are some gotcha's I found in the process, and a working .Net implementation.
陷阱
配置Webhook时
- 在Webhook URL中,不要指定端口
- 单击保存"时,该服务必须已启动并且正在运行,并且可用于来自Zoho的呼叫.这是因为Zoho在单击保存"后立即进行了一次测试调用,并在未通过http调用的情况下拒绝存储您的更改.这意味着您的开发或测试箱需要公开可用.对我来说,在Webhook中指定IP地址无效,我需要一个域名.无法从Internet访问开发人员计算机,最后我不得不使用动态DNS条目和路由器端口转发从家庭环境中测试集成.
- 就我而言,使用DELETE http动词不起作用. Zoho始终将完整的JSON正文或仅一对大括号附加到调用URL,从而使API调用失败.但是,Zoho支持人员声称这种方法应该可行,并且在他们的测试环境中不会发生此问题.
- 在计算哈希值之前,您需要构建一个字符串以计算哈希值.从他们的文档中尚不清楚如何为JSON负载执行此操作(影响其原始"和默认有效负载"设置).网络上一些未回答的问题询问是否需要反序列化,拼合,订购.答案是否定的,仅使用从http请求中检索到的有效负载即可.因此,组装字符串的正确方法是:获取URL查询参数(如果有的话)和表单字段(如果有的话),然后按键将它们按字母升序排序,然后将其键和值字符串附加到字符串中,且不带引号,空格,等号迹象.将http呼叫内容(如果有的话)附加到该字符串,而不进行任何处理.
- 在散列生成中,使用UTF8Encoding.
- 在Zoho默认有效负载和原始数据的情况下使用的Http标头:Content-Type = application/json; charset = UTF-8和X-Zoho-Webhook-Signature = $ {Generated Hash Value}.使用x-www-form-urlencoded Webhooks时:Content-Type = application/x-www-form-urlencoded和X-Zoho-Webhook-Signature = $ {Generate Hash Value}
代码
我们的验证是通过ASP-Net过滤器实现的,我删除了该位,以专注于哈希计算位.
Our validation was implemented as an ASP-Net filter, I removed that bit to concentrate on the hash calculation bit.
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();
}
这篇关于如何实现Zoho Subscriptions Webhook哈希验证?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!