如何实现Zoho Subscriptions Webhook哈希验证? [英] How to implement Zoho Subscriptions webhook hash validation?

查看:100
本文介绍了如何实现Zoho Subscriptions Webhook哈希验证?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将服务与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哈希验证?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆