65.9K
CodeProject 正在变化。 阅读更多。
Home

PHP/Laravel 中的 Alexa 请求验证

starIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

1.00/5 (1投票)

2018年9月27日

CPOL
viewsIcon

3691

本技巧展示了如何根据 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;
}
© . All rights reserved.