Laravel 5.7 签名路由 returns 403 签名无效

Laravel 5.7 signed route returns 403 invalid signature

我正在尝试利用 Laravel 5.7 中新的签名中间件,但由于某种原因,生成的签名 URL 返回 403 无效签名。

我使用的是最新的 Laravel 版本,PHP 7.2

这是我的 web.php 路线:

Route::get('/report/{user}/{client}', function ($user, $client) {
    return ("El usuario es: $user y el cliente es: $client");
})->name('report.client')->middleware('signed');

这是在我的控制器中:

$objDemo->tempURL = Url::temporarySignedRoute('report.client', now('America/Panama')->addDays(5), [
            'user' => 1,
            'client' => 1
        ]);

生成 URL 并显示如下内容:

https://example.com/report/1/1?expires=1545440368&signature=55ad67fa049a74fe8e123c664e50f53564b76154e2dd805c5927125f63c390a1

但是当我点击 link 时,结果是 403 消息:"Invalid signature"

有什么想法吗?提前致谢

------------更新------------

我已经完成的事情:

  1. 尝试不签名的路线,完美运行
  2. 试试不带参数只签名的路由
  3. 试试不临时设置只签名的路线
  4. 将 cloudflare 的 ip 设置为受信任的代理
  5. 禁用 HTTPS,启用 HTTPS

似乎没有任何效果,总是得到 403 无效签名页

------------更新 2------------

好的,所以经过一些挖掘和测试,我发现如果用户登录,laravel 签名的路由将不起作用,这很奇怪,如果我注销然后路由完美运行,但是如果我 log-in 然后它显示 403 错误,这可能是因为 Laravel 在其他所有内容之后添加了 session cookie header?所以签名的路线因此而失败?就应该这样吗?

很奇怪,假设我想为我的用户创建一个临时的 link 来下载一些东西,如果他们登录到我的 Laravel 应用程序,他们会得到这个403 错误消息...:(

------------更新 3-----------------

我尝试了 laravel 的全新安装并且工作完美,所以它来自我的主要 Laravel 应用程序,还尝试将每个作曲家依赖项安装到 [=69= 的全新安装中]],无论用户登录状态如何,仍然可以完美运行,因此与我的依赖项不冲突。

在调试 UrlGenerator::hasValidSignature() 之后,我通过 DD 结束了 UrlGenerator.php 中的变量,如下所示:

public function hasValidSignature(Request $request, $absolute = true)
    {
        $url = $absolute ? $request->url() : '/'.$request->path();

        //dd($url);

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

        dd($original);
        $expires = Arr::get($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);
    }

$original 变量向我展示了我的 URL 实际发生了什么,并展示了这个:

https://example.com/report/1/1?expires=1546586977&settings%5Bincrementing%5D=1&settings%5Bexists%5D=1&settings%5BwasRecentlyCreated%5D=0&settings%5Btimestamps%5D=1&profile%5Bincrementing%5D=1&profile%5Bexists%5D=1&profile%5BwasRecentlyCreated%5D=0&profile%5Btimestamps%5D=1&user%5Bincrementing%5D=1&user%5Bexists%5D=1&user%5BwasRecentlyCreated%5D=0&user%5Btimestamps%5D=1

如您所见,在 expires 参数之后有参数,这些参数在路由创建后添加,这就是问题所在,发生这种情况是因为我有一个中间件像这样向视图共享一些信息:

UserDataMiddleware.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
use App\User;
use App\Setting;
use App\UserProfile;
use Illuminate\Support\Facades\View;

class UserData
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {

        if (Auth::check()) {
            $settings = Setting::where('user_id', Auth::user()->id)->first();
            $profile = UserProfile::where('user_id', Auth::id())->first();
            $user = Auth::user();

            View::share('settings', $settings); //Another way to share variables, with the View::share
            View::share('profile', $profile);

            //Now we need to share owr variables trough the REQUEST to our controllers
            $request->merge([
                'settings' => $settings,
                'profile' => $profile,
                'user' => $user
            ]);


        }
        return $next($request);
    }
}

这个中间件在中间件​​组里面,所以这就是问题所在,如果将来有人试验这个,那么它可以先检查一下。

如果您使用的是 Heroku、AWS 或任何其他使用 LoadBalancer 的服务。还要确保到达您的应用程序的代理是可信的。

有关更多信息,请参阅

试试下面的代码:

class TrustProxies extends Middleware
{
    protected $proxies = '*';
    protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

基本上你的签名不匹配,因为你通过 \Illuminate\Support\Facades\URL::signedRoute 生成的 URL 被你的中间件改变了,这意味着当它检查 $request->hasValidSignature() 这个返回错误。

我有一个类似的问题,SendGrid 将 UTM 跟踪查询字符串添加到我电子邮件中的 URL (&utm_campaign=website&utm_source=sendgrid.com &utm_medium=email),它改变了 URL 并最终改变了签名。

当我破解时,我将以下代码添加到我的控制器中以去除额外的查询参数并重新使用签名:

// Fix issue with sendgrid highjacking signed URL's with extra query params..
if ($request->query('utm_campaign')) {
    $sig = $request->query('signature', '');
    $url = route('route-key') . '?signature=' . $sig;

    return redirect($url);
}

我在 dusk 遇到过类似的问题, .env.dusk.testing 中的 APP_KEY 与 .env

中的 APP_KEY 不匹配

我刚遇到这个问题,结果 URL 中的空参数永远不会生效。所以当你这样做时:

URL::temporarySignedRoute('newsletter.verify', now()->addDays(3), ['name' => $name, 'email' => $email])

但名称是一个空字符串(因为它不是强制性的),URL 将作为查询字符串的一部分与 name= 一起生成,但是 Laravel[=15 中的代码=]

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

不会 return 空的 name,因此 URL 是 'altered' 并且验证失败。常用的中间件ConvertEmptyStringsToNull可能与此有关

我遇到了同样的问题,并且在我偶然发现@LaravDev 的回答之前一直在发疯。

注意::我使用的 Laravel 7 与 web.php 页面上的不同

我原来的代码是这样的,它本质上只是在请求中添加了一个变量来告诉我的视图不要显示侧边栏。

Route::middleware(['noSidebar'])->group(function()
{
    Auth::routes(['verify' => true]);
});

我不得不删除 Auth::routes() 短代码并将其切换为完整的 Auth 路由堆栈。 (请注意,Laravel 的每个版本都不同)

Route::middleware(['noSidebar'])->group(function()
{

// Authentication Routes...
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login', 'Auth\LoginController@login');
Route::post('logout', 'Auth\LoginController@logout')->name('logout');

// Registration Routes...
Route::get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
Route::post('register', 'Auth\RegisterController@register');

// Password Reset Routes...
Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
Route::post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update');

// Confirm Password (added in v6.2)
Route::get('password/confirm', 'Auth\ConfirmPasswordController@showConfirmForm')->name('password.confirm');
Route::post('password/confirm', 'Auth\ConfirmPasswordController@confirm');

// Email Verification Routes...
Route::get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
Route::post('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
    
});



//Moved the routes with tokens in the URL to outside my middleware grouping.

Route::get('email/verify/{id}/{hash}', 'Auth\VerificationController@verify')->name('verification.verify');
Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');

Tada 有用! 谢谢大家

我在 .env 文件中有 APP_URL=http://localhost。当我从服务器将值更改为 URL 时,问题就解决了。

我正在使用 Laravel 8+

了解LARAVEL电子邮件验证方式

了解校验方式,可以帮助你简单的解决这个错误。

laravel 使用方法 URL::temporarySignedRoute(),

创建临时签名 url

此方法在 verificationUrl() 中调用,位于 \vendor\laravel\framework\src\Illuminate\Auth\Notifications\VerifyEmail.php.

/**
 * Get the verification URL for the given notifiable.
 *
 * @param mixed $notifiable
 * @return string
 */
protected function verificationUrl($notifiable)
{
   return URL::temporarySignedRoute(
        'verification.verify',
        Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
        [
            'id' => $notifiable->getKey(),
            'hash' => sha1($notifiable->getEmailForVerification()),
        ]
    );
}

URL::temporarySignedRoute()根据默认设置为.env('APP_URL')config('app.url)生成urls。

因此,如果发送到电子邮件的 url 与 url 不同,laravel 将在验证时收到(检查 url 的签名时) , 403 |出现无效签名。

示例:

  • 如果将 APP_URL 设置为 http://yourdomain.com/,验证 link 应该类似于 http://yourdomain.com/email/verify/{id}/{hash}。现在,如果您将服务器配置设置为重定向到 https,则会出现无效签名,因为 url laravel 获取的是 https://yourdomain.com/email/verify/{id}/{hash} 而不是电子邮件验证 url.