2013-08-22 47 views
0

我正在爲Shopify webhook實現一個簡單的Web服務,以使用Play2進行調用。我想使用包含的'X-Shopify-Hmac-Sha256'標頭參數來驗證來自Shopify的呼叫。Shopify驗證Scala中的webhook調用

Shopify文檔只包含一個Ruby和Php示例,不是很難翻譯我認爲。好吧,我似乎在掙扎。

這裏是我的簡單的斯卡拉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輸出格式)。

任何提示/指導/建議感激地收到。

回答

1

感謝csaunders指着我在正確的方向。

我使用默認的BodyParser AnyContent,當請求的內容類型指定'application/json'時,隱式地將響應正文轉換爲json。

我不得不修改我的控制器對象到指定的「原始」 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,但多數民衆贊成在沒有真正的問題。

現在所有工作都按預期工作。

感謝,

1

在原始POST身體剛剛讀取和運行驗證你對那個簽名。通過將身體抓取爲JSON並將其轉換爲字符串,您可能會巧妙地操縱我們發送給您的響應。

下面是我做它,我已經與網絡掛接(紅寶石)工作過的幾個項目:

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 
+0

感謝csaunders。是的你是對的,我沒有得到原始請求體,而是一個預先解析的版本。 – glidester