PHP/Laravel 中的 Alexa 请求验证





1.00/5 (1投票)
本技巧展示了如何根据 Amazon 的要求验证 Alexa 的请求。
引言
在使用自定义端点连接到 Laravel 后端系统开发 Alexa Skill 时,由于 Amazon 严格的安全要求,我在正确进行验证方面遇到了重大问题。这归因于 Amazon 或 **基本上任何人** 缺乏文档。
我为此工作了相当一段时间,尽管最终的解决方案看起来很简单。
该解决方案现在已经稳定运行了一个月,约 250 名用户持续使用,没有出现任何错误。因此,可以安全地假设它工作正常。
Using the Code
这是 Laravel 函数。
$request
是 Laravel 的 Request
对象。
public static function validateRequest($request)
{
$data = json_decode($request->getContent(), false);
if (!$request->hasHeader("SignatureCertChainUrl") | !$request->hasHeader("Signature"))
return false;
$certificateUri = $request->header("SignatureCertChainUrl");
$uriParts = parse_url($certificateUri);
if (strcasecmp($uriParts["scheme"], "https") != 0)
return false;
if (strcasecmp($uriParts["host"], "s3.amazonaws.com") != 0)
return false;
if (array_key_exists("port", $uriParts) && $uriParts["port"] != "443")
return false;
if (strpos($uriParts["path"], "/echo.api/") !== 0)
return false;
$certificateName = "certificates\\" . array_last(explode("/", $uriParts["path"]));
if (!Storage::disk("alexa")->exists($certificateName))
Storage::disk("alexa")->put($certificateName, file_get_contents($certificateUri));
$certificateFile = Storage::disk("alexa")->get($certificateName);
$certificateData = openssl_x509_parse($certificateFile);
$validFrom = date("Y-m-d H:i:s", $certificateData["validFrom_time_t"]);
$validTo = date("Y-m-d H:i:s", $certificateData["validTo_time_t"]);
$san = substr($certificateData["extensions"]["subjectAltName"],
strpos($certificateData["extensions"]["subjectAltName"], ":") + 1);
if (!Carbon::now()->between(Carbon::parse($validFrom), Carbon::parse($validTo)))
return false;
if ($san != "echo-api.amazon.com")
return false;
$publicKey = openssl_get_publickey(openssl_x509_read($certificateFile));
$signature = base64_decode($request->header("Signature"), true);
$hash1Binary = null;
openssl_public_decrypt($signature, $hash1Binary, $publicKey);
$hash1 = substr(bin2hex($hash1Binary), 30);
$hash2 = sha1($request->getContent());
if ($hash1 != $hash2)
return false;
if (!Carbon::now() > Carbon::createFromFormat("Y-m-d\TH:i:s\Z", $data->request->timestamp))
return false;
//finally
return true;
}