用于电子邮件验证的签名路由未通过签名验证

Way*_*her 3 php laravel laravel-5.7

我最近将我的项目从 Laravel 5.6 更新到 5.7,并将 Laravel 文档中描述的电子邮件验证步骤添加到我的项目中。在我的开发机器(http)上一切正常,但是当我用所有更改更新我的生产服务器(https)时,当 Laravel 向我发送带有链接(签名路由)的电子邮件时,它为我生成了单击按钮或粘贴的链接进入我的浏览器 laravel 似乎无法验证它创建的签名。副作用是每次我单击按钮或将链接粘贴到浏览器中时,我都会收到错误消息:

403 抱歉,您无权访问此页面。

到目前为止我所追踪的是我在 laravel 的 ValidateSignature.php 类中找到了代码,并添加了一些日志消息。

public function handle($request, Closure $next)
{
    Log::info('checking signature');
    if ($request->hasValidSignature()) {
        Log::info('signature is valid');
        return $next($request);
    }

    Log::info('throwing InvalidSignatureException');
    throw new InvalidSignatureException;
}
Run Code Online (Sandbox Code Playgroud)

更具体地说,我在 laravel 单元 UrlGenerator.php 中追踪了确切的问题,我在以下方法中添加了日志:

public function hasValidSignature(Request $request)
{
    $original = rtrim($request->url().'?'.Arr::query(
        Arr::except($request->query(), 'signature')
    ), '?');

    $expires = Arr::get($request->query(), 'expires');

    $signature = hash_hmac('sha256', $original, call_user_func($this->keyResolver));

    Log::info('url: '.$original);
    Log::info('expire: '.$expires);
    Log::info(' new signature: '.$signature);
    Log::info('link signature: '.$request->query('signature', ''));
    Log::info('hash equals: '.hash_equals($signature, $request->query('signature', '')));
    Log::info('expired: '.!($expires && Carbon::now()->getTimestamp() > $expires));

    return  hash_equals($signature, $request->query('signature', '')) &&
           ! ($expires && Carbon::now()->getTimestamp() > $expires);
}
Run Code Online (Sandbox Code Playgroud)

当我在浏览器中单击按钮或粘贴链接并按回车键时,我收到以下日志消息:(出于明显的原因,我更改了我的真实域......不要尝试推销我的网站或其他东西)

checking signature
url: http://www.example.com/email/verify/2?expires=1538012234
expire: 1538012234
new signature: 1326b9e7402a51e0f05ddf1cb14f1e14852b4c5f0d1d6e726554806e7d85b4b1
link signature: e1d3ad5dc88faa8d8b0e6890ef60e216b75d26ef7ed5c6ab1cc661548e0ad8df
hash equals:
expired: 1
throwing InvalidSignatureException
Run Code Online (Sandbox Code Playgroud)

所以我不知道这个错误是在 Laravel 创建初始签名的逻辑中还是在它试图验证它的时候。然而,就像我说的,它在我的开发机器上运行良好。我清除了缓存,清除了路由,更新到最新代码,重新启动了服务器,我能想到的一切。任何帮助将不胜感激。

**** 更新 *****

我挖得更深一些,缩小了问题的范围。我不敢相信我昨晚没有看到这个。如果我们仔细查看上面列出的输出日志,就会发现一条日志消息

url: http://www.example.com/email/verify/2?expires=1538012234
Run Code Online (Sandbox Code Playgroud)

向我们展示了问题。所以正如我之前所说,我的开发机器是 http 但我的实时服务器是 https。我今天早上(经过 4 个小时的睡眠后)看到日志向我们显示,hasValidSignature() 方法中的逻辑以某种方式使用 http 而不是 https 获取路由。因此,当我返回到我的电子邮件时,电子邮件中的链接是 https,如果我将 url 粘贴到浏览器中,则它具有 https,并且在此逻辑返回 403 错误后,浏览器中的浏览器仍显示 https。所以现在我们可以专注于我的路由/url 如何转换为 http?我在这里真的很挣扎,因为我不知道该 url 是如何处理的,因为 /email/verify 甚至没有列在我的任何路由文件中(我知道),我不能说我明白要查找的内容这个引擎盖,所以我真的希望在这里得到一些帮助。

这里还有我的 .env 文件中的设置:

APP_USE_HTTPS=true
APP_URL=https://www.example.com
APP_ENV=production
Run Code Online (Sandbox Code Playgroud)

在 AppServiceProvider 的启动方法中,我有

public function boot()
{
    Schema::defaultStringLength(191);

    if (env('APP_USE_HTTPS'))
    {
        Log::info('forcing URLs to use https');
        \URL::forceScheme('https');
    }
Run Code Online (Sandbox Code Playgroud)

Jan*_*Jan 8

如果您在 apache 代理后面有一个 Laravel 应用程序,也会发生这种情况。在我们的例子中,我们或多或少有相同的 .env 配置,我们也有

URL::forceScheme('https'); 
Run Code Online (Sandbox Code Playgroud)

在我们的 AppServiceProvider 中。

这将创建以下网址: 签署签名时: https://..../email/verify/174?expires=1556027661 验证签名时: http://..../email/verify/174

我们的解决方法是替换“已签名”中间件:在 app/Http/Kernel.php 中使用'signed' => \App\Http\Middleware\ValidateHttpsSignature::class,然后使用以下代码创建此类:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Routing\Exceptions\InvalidSignatureException;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Carbon;

class ValidateHttpsSignature
{
    var $keyResolver;

    public function __construct()
    {
        $this->keyResolver = function () {
            return App::make('config')->get('app.key');
        };
    }

    /**
     * gebaseerd op vendor/laravel/framework/src/Illuminate/Routing/Middleware/ValidateSignature.php
     * maar zorgt er voor dat een url altijd als https behandeld wordt. dit fixt het feit dat
     * laravel achter een rewrite proxy draait en urls binnenkrijgt als http.
     *
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($this->hasValidSignature($request)) {
            return $next($request);
        }
        throw new InvalidSignatureException;

    }

    /**
     * Determine if the given request has a valid signature.
     * copied and modified from
     * vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php:363
     * @param  \Illuminate\Http\Request  $request
     * @param  bool  $absolute
     * @return bool
     */
    public function hasValidSignature(Request $request, $absolute = true)
    {
        $url = $absolute ? $request->url() : '/'.$request->path();

        // THE FIX:
        $url = str_replace("http://","https://", $url);

        $original = rtrim($url.'?'.Arr::query(
                Arr::except($request->query(), 'signature')
            ), '?');

        $expires = $request->query('expires');

        $signature = hash_hmac('sha256', $original, call_user_func($this->keyResolver));

        return  hash_equals($signature, (string) $request->query('signature', '')) &&
            ! ($expires && Carbon::now()->getTimestamp() > $expires);
    }

}
Run Code Online (Sandbox Code Playgroud)