我已经设置了一个端点来接收Shopify的webhook请求。
Shopify的请求包括从共享密钥和请求正文创建的HMAC标头。
我需要在服务器上计算HMAC并将其与请求标头中的值相匹配,以确保请求是真实的。
我似乎无法在.NET中创建适当的机制来创建匹配的HMAC值。
此时我的算法如下:
public static string CreateHash(string data)
{
string sharedSecretKey = "MY_KEY";
byte[] keyBytes = Encoding.UTF8.GetBytes(sharedSecretKey);
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
//use the SHA256Managed Class to compute the hash
System.Security.Cryptography.HMACSHA256 hmac = new HMACSHA256(keyBytes);
byte[] hmacBytes = hmac.ComputeHash(dataBytes);
//retun as base64 string. Compared with the signature passed in the header of the post request from Shopify. If they match, the call is verified.
return System.Convert.ToBase64String(hmacBytes);
}
可以找到用于验证其webhook的Shopify文档HERE,但仅包含PHP和Ruby示例。
任何人都可以看到我可能做错了吗?我应该只是将整个JSON请求体作为字符串传递给此方法吗?
答案 0 :(得分:7)
private static bool Validate(string sharedSecretKey)
{
var data = GetStreamAsText(HttpContext.Current.Request.InputStream, HttpContext.Current.Request.ContentEncoding);
var keyBytes = Encoding.UTF8.GetBytes(sharedSecretKey);
var dataBytes = Encoding.UTF8.GetBytes(data);
//use the SHA256Managed Class to compute the hash
var hmac = new HMACSHA256(keyBytes);
var hmacBytes = hmac.ComputeHash(dataBytes);
//retun as base64 string. Compared with the signature passed in the header of the post request from Shopify. If they match, the call is verified.
var hmacHeader = HttpContext.Current.Request.Headers["x-shopify-hmac-sha256"];
var createSignature = Convert.ToBase64String(hmacBytes);
return hmacHeader == createSignature;
}
private static string GetStreamAsText(Stream stream, Encoding encoding)
{
var bytesToGet = stream.Length;
var input = new byte[bytesToGet];
stream.Read(input, 0, (int)bytesToGet);
stream.Seek(0, SeekOrigin.Begin); // reset stream so that normal ASP.NET processing can read data
var text = encoding.GetString(input);
return text;
}
答案 1 :(得分:2)
正如您在问题中提到的那样,您应该在方法中对整个json请求体进行哈希处理。
我的.NET不太好,但这是ruby示例的一部分,向您展示了该做什么:
post '/' do
. . .
data = request.body.read
verified = verify_webhook(data, env["HTTP_X_SHOPIFY_HMAC_SHA256"])
. . .
end
你可以看到我们只是抓住请求的主体(作为一个字符串)并将其逐字地放入验证方法中。试一试,希望你能有更多的运气。
答案 2 :(得分:0)
作为上述代码的改进,您可以将其转换为具有一些小的更改的属性:
public class VerifyShopifyAttribute : ActionFilterAttribute
{
private readonly string sharedSecret = "abc";
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!ValidateHash(actionContext))
{
// reject the request with a 400 error
var response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Bad Request");
actionContext.Response = response;
}
}
private bool ValidateHash(HttpActionContext actionContext)
{
var context = (HttpContextBase)actionContext.Request.Properties["MS_HttpContext"];
context.Request.InputStream.Seek(0, SeekOrigin.Begin);
using (var stream = new MemoryStream())
{
context.Request.InputStream.CopyTo(stream);
string requestBody = Encoding.UTF8.GetString(stream.ToArray());
var keyBytes = Encoding.UTF8.GetBytes(sharedSecret);
var dataBytes = Encoding.UTF8.GetBytes(requestBody);
//use the SHA256Managed Class to compute the hash
var hmac = new HMACSHA256(keyBytes);
var hmacBytes = hmac.ComputeHash(dataBytes);
//retun as base64 string. Compared with the signature passed in the header of the post request from Shopify. If they match, the call is verified.
var hmacHeader = HttpContext.Current.Request.Headers["x-shopify-hmac-sha256"];
var createSignature = Convert.ToBase64String(hmacBytes);
return hmacHeader == createSignature;
}
}
}
然后您就可以将其像这样用于所有Webhooks:
[RoutePrefix("api")]
public class ShopifyWebHookController : ApiController
{
[VerifyShopify]
[HttpPost]
public IHttpActionResult HandleWebhook(...)
{
...
}
}
答案 3 :(得分:0)
我在使用 Azure 函数 3.0 框架的 Azure 函数中遇到了这个问题,该框架在 .NET Core 3.1 上使用 C#。受上述答案和我自己的修改启发,我能够使用以下代码完成此操作。
// replace the appsettings variable with however you get your pre-shared signature
// req is passed in after being injected through the Azure Functions framework
private static async Task<bool> ValidateShopifySignature(HttpRequest req, AppSettings appSettings)
{
var hmacHeader = req.Headers["X-Shopify-Hmac-Sha256"];
var sharedSignatureBytes = Encoding.UTF8.GetBytes(appSettings.ShopifyWebhookSignature);
using var hmac = new HMACSHA256(sharedSignatureBytes);
//copy the request body to a memory stream then convert it to a byte[]
using MemoryStream dataStream = new();
await req.Body.CopyToAsync(dataStream);
var dataBytes = dataStream.ToArray();
//compute a hash of the body based on the signature
var generatedHmacHashBytes = hmac.ComputeHash(dataBytes);
var generatedSignature = Convert.ToBase64String(generatedHmacHashBytes);
//compare that signature to the one that Shopify generated and sent over
return hmacHeader == generatedSignature;
}