我正在开发 Shopify 应用程序。我正在尝试使用此函数使用 Shopify 的 HMAC 身份验证来验证应用程序上发生的 Shopify 交易。 Webhook 是通过主题 app_subscriptions/update 的 Shopify Webhook API 创建的,shopifyApiSecret 是我们 Shopify 应用程序的客户端密钥。这是我们正在使用的代码片段,
async function validateHmac(req: Request) {
let shopifyApiSecret = config.shopifyAppSecret;
let hmac: any = req.headers["x-shopify-hmac-sha256"];
const message = JSON.stringify(req.body);
const generatedHash = crypto
.createHmac("sha256", shopifyApiSecret)
.update(message)
.digest("base64");
console.log({ message, generatedHash, hmac });
const signatureOk = crypto.timingSafeEqual(
Buffer.from(generatedHash),
Buffer.from(hmac)
);
if (signatureOk) {
return true;
} else {
return false;
}
}
我们尝试比较这两种方法,即使用 === 以及使用timingSafeEqual,但该函数总是返回 false 并且在检查时 generatedHash 和 hmac 不相等。谁能告诉我这个实现是否有任何问题?预先感谢。
这是我们在.Net 中完成的实现。你可以从下面的代码中理解其中的逻辑:
string hmacHeader = Convert.ToString(Request.Headers["X-Shopify-Hmac-SHA256"]);
string requestJson = JsonSerializer.Serialize(request);
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(APISecretKey));
string hash = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(requestJson)));
isSuccess = string.Equals(hmacHeader, hash, StringComparison.OrdinalIgnoreCase);
问题在于我们生成消息所使用的数据。 我们可以通过使用路由器中的express功能来解决这个问题
express.json({
limit: '10mb',
verify: (req, _res, buf) => {
(req as any).rawBody = buf;
},
});
并将 rawBody 传递给更新函数。
async function validateHmac(req: Request) {
let shopifyApiSecret = config.shopifyAppSecret;
let hmac: any = req.headers["x-shopify-hmac-sha256"];
const message = req.rawBody;
const generatedHash = crypto
.createHmac("sha256", shopifyApiSecret)
.update(message)
.digest("base64");
console.log({ message, generatedHash, hmac });
const signatureOk = crypto.timingSafeEqual(
Buffer.from(generatedHash),
Buffer.from(hmac)
);
if (signatureOk) {
return true;
} else {
return false;
}
}
这是 .NET 8 使用依赖注入获取共享密钥的解决方案。
创建一个
ShopifySettings
类:
public class ShopifySettings
{
public string ApiSecretKey { get; set; }
}
然后将设置放入您的
appsettings.json
:
"ShopifySettings": {
"ApiSecretKey": "<YOUR SECRET>",
}
在您的
Program.cs
文件中,在 var builder = WebApplication.CreateBuilder(args);
之后添加以下内容,在 var app = builder.Build();
之前添加以下内容:
builder.Services.Configure<ShopifySettings>(builder.Configuration.GetSection(nameof(ShopifySettings)));
以及
var app = builder.Build();
之后和app.MapControllers();
之前:
app.Use(next => context =>
{
context.Request.EnableBuffering();
return next(context);
});
然后创建
VerifyShopifyAttribute
类:
public class VerifyShopifyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext actionContext)
{
if (!(ValidateHash(actionContext)))
{
throw new BadHttpRequestException("Bad Request");
}
}
private bool ValidateHash(ActionExecutingContext actionContext)
{
actionContext.HttpContext.Request.Body.Position = 0;
using var stream = new MemoryStream();
actionContext.HttpContext.Request.Body.CopyToAsync(stream).Wait();
var requestBody = Encoding.UTF8.GetString(stream.ToArray());
var svc = actionContext.HttpContext.RequestServices;
var shopifySettings = svc.GetService<IOptions<ShopifySettings>>()?.Value;
var keyBytes = Encoding.UTF8.GetBytes(shopifySettings.ApiSecretKey);
var dataBytes = Encoding.UTF8.GetBytes(requestBody);
var hmac = new HMACSHA256(keyBytes);
var hmacBytes = hmac.ComputeHash(dataBytes);
var hmacHeader = actionContext.HttpContext.Request.Headers["x-shopify-hmac-sha256"];
var createSignature = Convert.ToBase64String(hmacBytes);
return hmacHeader == createSignature;
}
}
并将其添加到您想要验证的控制器的 webhook 方法中:
[HttpPost("~/order")]
[VerifyShopify]
public async Task<IActionResult> Post()
{
return Ok();
}