【问题标题】:Nodejs - Expressjs - Verify shopify webhookNodejs - Expressjs - 验证 shopify webhook
【发布时间】:2016-04-20 13:22:11
【问题描述】:

我正在尝试在开发环境中验证从 shopify webhook 发送的 hmac 代码。但是shopify 不会将webhook 的发布请求发送到非实时端点,因此我使用requestbin 捕获请求,然后使用postman 将其发送到我的本地网络服务器。

来自shopify documentation,我似乎做的一切都是正确的,并且也尝试过应用node-shopify-auth verifyWebhookHMAC function 中使用的方法。但到目前为止,这些都没有奏效。 代码永远不会匹配。 我在这里做错了什么?

我的验证 webhook 的代码:

 function verifyWebHook(req, res, next) {
      var message = JSON.stringify(req.body);
    //Shopify seems to be escaping forward slashes when the build the HMAC
        // so we need to do the same otherwise it will fail validation
        // Shopify also seems to replace '&' with \u0026 ...
        //message = message.replace('/', '\\/');
        message = message.split('/').join('\\/');
    message = message.split('&').join('\\u0026');
      var signature = crypto.createHmac('sha256', shopifyConfig.secret).update(message).digest('base64');
      var reqHeaderHmac = req.headers['x-shopify-hmac-sha256'];
      var truthCondition = signature === reqHeaderHmac;

      winston.info('sha256 signature: ' + signature);
      winston.info('x-shopify-hmac-sha256 from header: ' + reqHeaderHmac);
      winston.info(req.body);

      if (truthCondition) {
        winston.info('webhook verified');
        req.body = JSON.parse(req.body.toString());
        res.sendStatus(200);
        res.end();
        next();
      } else {
        winston.info('Failed to verify web-hook');
        res.writeHead(401);
        res.end('Unverified webhook');
      }
    }

我收到请求的路由:

router.post('/update-product', useBodyParserJson, verifyWebHook, function (req, res) {
  var shopName = req.headers['x-shopify-shop-domain'].slice(0, -14);
  var itemId = req.headers['x-shopify-product-id'];
  winston.info('Shopname from webhook is: ' + shopName + ' For item: ' + itemId);
});

【问题讨论】:

    标签: node.js shopify hmac webhooks


    【解决方案1】:

    我做的有点不同——不确定我在哪里看到了推荐,但我在正文解析器中进行了验证。 IIRC 的一个原因是我可以在任何其他处理程序可能接触到原始身体之前访问它:

    app.use( bodyParser.json({verify: function(req, res, buf, encoding) {
        var shopHMAC = req.get('x-shopify-hmac-sha256');
        if(!shopHMAC) return;
        if(req.get('x-kotn-webhook-verified')) throw "Unexpected webhook verified header";
        var sharedSecret = process.env.API_SECRET;
        var digest = crypto.createHmac('SHA256', sharedSecret).update(buf).digest('base64');
        if(digest == req.get('x-shopify-hmac-sha256')){
            req.headers['x-kotn-webhook-verified']= '200';
        }
     }})); 
    

    然后任何网络钩子只处理已验证的标头:

    if('200' != req.get('x-kotn-webhook-verified')){
        console.log('invalid signature for uninstall');
        res.status(204).send();
        return;
    }
    var shop = req.get('x-shopify-shop-domain');
    if(!shop){
        console.log('missing shop header for uninstall');
        res.status(400).send('missing shop');
        return;
    }
    

    【讨论】:

    • 注意:从技术上讲,204 应该是 403,但我更愿意默默地让任何调用者认为他们成功了。
    • 这仍然对我不起作用 :( 你如何在开发环境中测试它,因为 shopify 不会向开发端点发送 webdhooks 请求?
    • 我还记录了 req.body,就在您定义 shopHAMC 之前,它是一个空对象。此时它应该还是空的吗?
    • 此时没有定义正文。它实际上是由验证后运行的主体解析器设置的。使用缓冲区。就测试而言,我只是在安装应用程序时注册网络书。由于我的应用程序没有在应用程序商店上发布,我只使用了我的实时 URL。您也可以为此使用 ngrok。
    • 您打算在哪里托管实时应用程序?如果您不能使用它,请尝试使用 ngrok 而不是 requestb.in 和 postman。
    【解决方案2】:

    简答

    express 中的正文解析器不能很好地处理 BigInt,并且以整数形式传递的订单号之类的东西会损坏。除此之外,某些值被编辑,例如 URL 最初发送为“https://...”,OP 也从其他代码中发现了这一点。

    为了解决这个问题,不要使用正文解析器解析数据,而是将其作为原始字符串获取,稍后您可以使用 json-bigint 对其进行解析,以确保它没有被损坏。

    长答案

    尽管@bknights 的answer 工作得非常好,但首先要找出发生这种情况的原因很重要。

    对于我在 Shopify 的“order_created”事件上创建的 webhook,我发现传递给正文的请求的 id 与我从测试数据中发送的不同,结果证明这是一个问题express 中的 body-parser 不能很好地处理大整数。

    最终我将一些东西部署到谷歌云功能并且 req 已经有我可以使用的原始正文,但是在我的 Node 测试环境中,我将以下内容作为单独的正文解析器实现了,因为使用相同的正文解析器两次覆盖了原始正文带有 JSON 的正文

    var rawBodySaver = function (req, res, buf, encoding) {
        if (buf && buf.length) {
          req.rawBody = buf.toString(encoding || 'utf8');
        }
    }
    app.use(bodyParser.json({verify: rawBodySaver, extended: true}));
    

    基于this的回答

    我稍后使用 json-bigint 解析 rawBody 以用于其他地方的代码,否则某些数字已损坏。

    【讨论】:

    【解决方案3】:

    // Change the way body-parser is used
    const bodyParser = require('body-parser');
    
    var rawBodySaver = function (req, res, buf, encoding) {
        if (buf && buf.length) {
            req.rawBody = buf.toString(encoding || 'utf8');
        }
    }
    app.use(bodyParser.json({ verify: rawBodySaver, extended: true }));
    
    
    // Now we can access raw-body any where in out application as follows
    // request.rawBody in routes;
    
    // verify webhook middleware
    const verifyWebhook = function (req, res, next) {
        console.log('Hey!!! we got a webhook to verify!');
    
        const hmac_header = req.get('X-Shopify-Hmac-Sha256');
        
        const body = req.rawBody;
        const calculated_hmac = crypto.createHmac('SHA256', secretKey)
            .update(body,'utf8', 'hex')
            .digest('base64');
    
        console.log('calculated_hmac', calculated_hmac);
        console.log('hmac_header', hmac_header);
    
        if (calculated_hmac == hmac_header) {
            console.log('Phew, it came from Shopify!');
            res.status(200).send('ok');
            next();
        }else {
            console.log('Danger! Not from Shopify!')
            res.status(403).send('invalid');
        }
    
    }

    【讨论】:

      【解决方案4】:

      有同样的问题。使用 request.rawBody 代替 request.body 有帮助:

      import Router from "koa-router";
      import koaBodyParser from "koa-bodyparser";
      import crypto from "crypto";
      
      ...
      
      koaServer.use(koaBodyParser()); 
      
      ...
      
      koaRouter.post(
          "/webhooks/<yourwebhook>",
          verifyShopifyWebhooks,
          async (ctx) => {
            try {
              ctx.res.statusCode = 200;
            } catch (error) {
              console.log(`Failed to process webhook: ${error}`);
            }
          }
      );
      
      ...
      
      async function verifyShopifyWebhooks(ctx, next) {
        const generateHash = crypto
          .createHmac("sha256", process.env.SHOPIFY_WEBHOOKS_KEY) // that's not your Shopify API secret key, but the key under Webhooks section in your admin panel (<yourstore>.myshopify.com/admin/settings/notifications) where it says "All your webhooks will be signed with [SHOPIFY_WEBHOOKS_KEY] so you can verify their integrity
          .update(ctx.request.rawBody, "utf-8")
          .digest("base64");
      
        if (generateHash !== shopifyHmac) {
          ctx.throw(401, "Couldn't verify Shopify webhook HMAC");
        } else {
          console.log("Successfully verified Shopify webhook HMAC");
        }
        await next();
      }

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多