我从 Manager One API 收到传入传输事件 Webhook。对于签名验证,他们采用HMAC-SHA256 算法。他们为我提供了一个共享密钥,它是我放在 ENV[“MANAGER_ONE_SECRET_SIGNATURE”] 中的数字和字母的组合。对于此示例,我们假设该值为“shared_secret_key”。我一直致力于将从请求标头信息中获取的密钥与此共享密钥进行匹配,以成功验证 Webhook。
这是我迄今为止所取得的成就:
class Api::ManagerOneController < Api::ApplicationController
def create
if expected_signature == provided_signature_base64
request_body = JSON.parse(request.raw_post)["incoming_bank_operation"]
ManagerOne::Webhooks::IncomingTransferJob.perform_later(request_body)
render status: :ok, json: { message: "Signature verified successfully" }
else
render status: :unauthorized, json: { error: "Unauthorized" }
end
end
private
def expected_signature
Base64.strict_encode64(
OpenSSL::HMAC.digest(
OpenSSL::Digest.new("sha256"),
ENV["MANAGER_ONE_SECRET_SIGNATURE"],
signature_input
)
)
end
def signature_input
content_length = "\"content-length\": #{request.headers['Content-Length']}\n"
user_agent = "\"user-agent\": #{request.headers['User-agent']}\n"
content_type= "\"content-type\": #{request.headers['Content-Type']}\n"
host = "\"host\": #{request.headers['Host']}\n"
request_target = "\"(request-target)\": #{"post #{request.path}"}\n"
date = "\"date\": #{request.headers['Date']}\n"
digest = "\"digest\": #{request.headers['Digest']}\n"
signature_params = "\"@signature-params\": (\"content-length\" \"user-agent\" \"content-type\" \"host\" \"(request-target)\" \"date\" \"digest\"); keyid=\"#{ENV["MANAGER_ONE_SECRET_SIGNATURE"]}\"; algorithm=\"hmac-sha256\";"
"#{content_length}#{user_agent}#{content_type}#{host}#{request_target}#{date}#{digest}#{signature_params}"
end
def provided_signature_base64
request.headers['Signature'].match(/signature="([^"]+)"/)[1]
end
end
标头随每个请求而变化;我提供的数据只是一个例子:
expected_signature = "1zmbBUVFEsqlj8G0h8SPdikbu7et4boR/r/HHoLkr2o="
我按照本文档中提供的说明生成了signature_input:
signature_input = "\"content-length\": 1107\n\"user-agent\": manager.one\n\"content-type\": application/json\n\"host\": localhost:3000\n\"(request-target)\": post /api/manager_one/webhooks\n\"date\": Mon, 30 Oct 2023 13:02:55 GMT\n\"digest\": SHA-256=sTz9SzsDH3WGcbYSZb+G8Xk1javIUxE0egQaKD1Wyws=\n\"@signature-params\": (\"content-length\" \"user-agent\" \"content-type\" \"host\" \"(request-target)\" \"date\" \"digest\"); keyid=\"shared_secret_key\"; algorithm=\"hmac-sha256\";"
我应该与预期签名匹配的密钥是我从 Manager One API 文档示例中获得的密钥,尽管它会随每个请求而变化:
provided_signature_base64 = "pkLgUJmM5FixErazNavlwWizmDZdTDBcMZb9xY3ZUv8="
我错过了什么吗?我不知道我做错了什么。
class Api::ManagerOneController < Api::ApplicationController
def create
success = expected_signature == provided_signature_base64
request_body = JSON.parse(request.raw_post)["incoming_bank_operation"]
ManagerOne::Webhooks::IncomingTransferJob.perform_later(request_body, success)
render status: :ok, json: { success: true, message: success ? "Authorized" : "Unauthorized" }
end
private
def expected_signature
hmac = OpenSSL::HMAC.digest("SHA-256", ENV["MANAGER_ONE_SECRET_SIGNATURE"], signature_input)
Base64.encode64(hmac).chomp
end
def signature_input
content_length = "content-length: #{request.headers['Content-Length']}"
user_agent = "user-agent: #{request.headers['User-agent']}"
content_type= "content-type: #{request.headers['Content-Type']}"
# When using Ngrok in development environment, host must be ngrok url & not localhost:3000
host = "host: #{request.headers['Host']}"
request_target = "(request-target): #{"post #{request.path}"}"
date = "date: #{request.headers['Date']}"
digest = "digest: #{request.headers['Digest']}"
"#{content_length}\n#{user_agent}\n#{content_type}\n#{host}\n#{request_target}\n#{date}\n#{digest}"
end
def provided_signature_base64
request.headers['Signature'].match(/signature="([^"]+)"/)[1]
end
end