电子邮件验证的签名路由未通过签名验证 [英] Signed route for email verification does not pass signature validation

查看:148
本文介绍了电子邮件验证的签名路由未通过签名验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I recently updated my project from Laravel 5.6 to 5.7 and added the email verification steps described by Laravel docs to my project. Everything works great on my development machine (which is http) but when I update my production server (which is https) with all changes then when laravel sends me the email with the link (signed route) it generated for me to click button or paste into my browser laravel seems to not be able to validate the signature it created. The side effect is every time I click the button or paste the link into the browser I get the error:

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

403 Sorry, you are not authorized to access this page.

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

What I have traced down so far is I found the code in laravel's ValidateSignature.php class and I added some log messages.

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;
}

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

And more specifically I traced the exact issue inside the laravel unit UrlGenerator.php I added the Logs in the following method:

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);
}

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

When i click button or paste link in browser and press enter I get the following log messages: (I changed my real domain for obvious reasons.... not try to market my site or something)

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

所以我不知道该错误是在laravel创建初始签名的逻辑上还是在试图验证它的逻辑上. 但是就像我说的那样,这一切在我的开发机器上都很好用.我已经清除了缓存,清除了路由,更新为最新代码,重新启动了服务器,所有我能想到的. 任何帮助将不胜感激.

So I don't know if the bug is in the logic where laravel creates initial signature or when it is trying to validate it. However like I said it all works great on my development machine. I have cleared cache, cleared routes, updated to latest code, rebooted server, everything I can think of. Any help would be greatly appreciated.

****更新*****

**** UPDATE *****

我更深入地挖掘了问题,并缩小了范围. 我不敢相信我昨晚没看到这个.如果我们仔细查看一则日志消息上方列出的输出日志

I dug a little deeper and have narrowed down the problem. I can't believe I didn't see this last night. If we look closely at the output logs listed above the one log message

url: http://www.example.com/email/verify/2?expires=1538012234

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

shows us the problem. So as I said before my development machine is http but my live server is https. I see this morning (after a good 4 hours sleep) that the log shows us that somehow the logic in the method hasValidSignature() is getting a route with http instead of https. So when I go back to my email the link in the email is https, if I paste the url in my browser it has https, and in my browser after this logic returns the 403 error the browser still shows https. So now we can focus on how does my route/url get converted to http? I am really struggling here cause I have no idea how that url is processed anyhow since /email/verify is not even listed in any of my routes files (that I know of) and I can't say I understand what to look for under the hood for this either so I am really hoping for some help here.

这也是我的.env文件中的设置:

Also here are the settings in my .env file:

APP_USE_HTTPS=true
APP_URL=https://www.example.com
APP_ENV=production

在AppServiceProvider的启动方法中,我已经拥有

And in the boot method of the AppServiceProvider I have

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

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

推荐答案

如果您在Apache代理后面有Laravel应用,也会发生这种情况.就我们而言,我们具有大致相同的.env配置,并且我们也有

If you have a Laravel app behind an apache proxy this also happens. In our case, we have more or less the same .env configuration and we also have

URL::forceScheme('https'); 

在我们的AppServiceProvider中.

in our AppServiceProvider.

这将创建以下URL:在对签名进行签名时: https://..../email/verify/174?expires = 1556027661 验证签名时: http://..../email/verify/174

This creates the following urls: while signing the signature: https://..../email/verify/174?expires=1556027661 While verifying the signature: http://..../email/verify/174

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

our workaround is to replace the the 'signed' middleware: in app/Http/Kernel.php use 'signed' => \App\Http\Middleware\ValidateHttpsSignature::class, and then create this class with the following code:

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);
    }

}

这篇关于电子邮件验证的签名路由未通过签名验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆