1

Play2 を使用して呼び出すための Shopify Webhook 用の単純な Web サービスを実装しています。含まれている「X-Shopify-Hmac-Sha256」ヘッダー パラメーターを使用して、Shopify からの呼び出しであることを確認したいと考えています。

Shopify ドキュメントには Ruby と Php のサンプルのみが含まれており、翻訳するのはそれほど難しくないと思いました。うーん、苦戦しているようです。

これが私の単純な Scala shopify util オブジェクトです。

    import play.api.mvc.Request
    import play.api.mvc.AnyContent
    import javax.crypto.Mac
    import javax.crypto.spec.SecretKeySpec
    import play.api.Logger
    import javax.crypto.SecretKey
    import org.apache.commons.codec.binary.Base64

    object ShopifyUtils {
        def verifyWebhookCall(request : Request[AnyContent], secretKey: String) : Boolean = {

          if (!request.headers.get("X-Shopify-Hmac-Sha256").isDefined)
              false
          else
          {
            val headerHash = request.headers.get("X-Shopify-Hmac-Sha256").getOrElse("")
            val body = request.body.asJson.get.toString

            Logger.info("json '" + request.body.asJson.get.toString + "' = " + encode(secretKey, request.body.asJson.get.toString) );
            Logger.info("body '" + request.body.toString() + "' = " + encode(secretKey, request.body.toString) )

            Logger.info("headerHash " + headerHash);

            val calcHash = encode(secretKey, body)
            headerHash.equals(calcHash)
          }
        }

        def encode(key: String , data: String): String = {
          val sha256_HMAC = Mac.getInstance("HmacSHA256");
          val secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");
          sha256_HMAC.init(secret_key);

          return new String( Base64.encodeBase64( sha256_HMAC.doFinal( data.getBytes ) ) ).trim
        }
    }

私が生成するハッシュは、Shopify が送信するものと同じになることはありません。

共有秘密鍵が間違っている (どのようになるかわかりません) か、Shopify と同じコンテンツをハッシュしていません (さまざまなrequest.body出力形式を試しました)。

感謝して受け取ったヒント/ガイド/提案。

ティム

4

3 に答える 3

2

生の POST 本文を読み込んで、それに対して署名を検証します。本文を JSON として取得して文字列に変換することで、送信される応答を微妙に操作している可能性があります。

以下は、Webhook を (Ruby で) 使用したいくつかのプロジェクトで行った方法です。

class WebhookVerifier
  attr_accessor :expected_hmac, :data
  def initialize(options = {})
    @expected_hmac = options.fetch(:expected_hmac, '')
    content = options.fetch(:content, StringIO.new)
    content.rewind
    @data = content.read
  end    

  def valid?
    digest = OpenSSL::Digest::Digest.new('sha256')
    calculated_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, ShopifyApp.configuration.secret, data)).strip
    calculated_hmac == expected_hmac
  end
end
于 2013-08-22T11:42:07.060 に答える
1

私を正しい方向に向けてくれた csaunders に感謝します。

AnyContentリクエストの Content-type が「application/json」を指定している場合、レスポンス本文を暗黙的に json に変換するデフォルトの BodyParser を使用していました。

「生の」BodyParser を指定するには、コントローラー オブジェクトを変更する必要がありました。

    import play.api._
    import play.api.libs.iteratee.Enumerator
    import play.api.mvc.SimpleResult
    import play.api.mvc.ResponseHeader
    import play.api.libs.json._
    import play.Application
    import play.api.mvc._

    import javax.crypto.Mac
    import javax.crypto.spec.SecretKeySpec
    import play.api.Logger
    import javax.crypto.SecretKey
    import org.apache.commons.codec.binary.Base64

    object Purchase extends Controller { 

      val shopifyAppSecretKey = "11111111111111111111111111111111"

      def processPurchase() = Action( parse.raw ) {request =>

        val bodyRaw = request.body.asBytes(3000).getOrElse(Array[Byte]())
        val calculatedHash = encodeByteArray(shopifyAppSecretKey, bodyRaw)
        val shopifyHash = request.headers.get("X-Shopify-Hmac-Sha256").getOrElse("")

        Logger.info("keys '" + shopifyHash + "' || '" + calculatedHash + "' " + calculatedHash.equals(shopifyHash))

        val json: JsValue = Json.parse( new String(bodyRaw) )

        Ok( "Ok" ).as(HTML)
      }

      def encodeByteArray(key: String , data: Array[Byte]): String = {
        val sha256_HMAC = Mac.getInstance("HmacSHA256");
        val secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");
        sha256_HMAC.init(secret_key);

        return new String( Base64.encodeBase64( sha256_HMAC.doFinal( data ) ) ).trim
      }
    }

「生」の BodyParser を使用すると、バイト配列を自分で文字列に変換し、その文字列を手動で解析して json を取得する必要がありますが、それは実際の問題ではありません。

現在、すべてが期待どおりに機能しています。

ありがとう、

ティム

于 2013-08-22T14:38:14.523 に答える