Laravel 5.7 签名路由返回 403 无效签名

Posted

技术标签:

【中文标题】Laravel 5.7 签名路由返回 403 无效签名【英文标题】:Laravel 5.7 signed route returns 403 invalid signature 【发布时间】:2019-05-22 08:57:03 【问题描述】:

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

我正在使用最新的 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

但是当我点击链接时,结果是 403 并显示消息:“无效签名”

有什么想法吗?提前谢谢

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

我已经做过的事情:

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

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

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

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

很奇怪,因为假设我想为我的用户创建一个临时链接以下载某些东西,如果他们登录到我的 Laravel 应用程序,他们将收到此 403 错误消息... :(

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

我尝试了全新安装的 laravel 并且运行良好,所以它来自我的主要 Laravel 应用程序,还尝试将每个 composer 依赖项安装到 Laravel 的全新安装中,无论用户登录状态如何,仍然可以正常运行,所以这与我的依赖项没有冲突。

【问题讨论】:

@LaraDev 你的挖掘绝对是正确的。 【参考方案1】:

了解 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()根据config('app.url)创建url,默认设置为.env('APP_URL')

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

示例:

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

【讨论】:

【参考方案2】:

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

我使用的是Laravel 8+

【讨论】:

【参考方案3】:

我遇到了同样的问题,直到我偶然发现了@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');

多田它有效! 谢谢大家

【讨论】:

谢谢,我疯了!我把我签名的网址放在里面:Route::localizedGroup(function () )【参考方案4】:

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

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

但是 name 是一个空字符串(因为它不是强制性的),URL 将使用 name= 作为查询字符串的一部分生成,但是 Laravel 中的这段代码

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

不会返回空的name,因此 URL 被“更改”并且验证失败。常用的中间件ConvertEmptyStringsToNull可能与此有关。

【讨论】:

【参考方案5】:

我在黄昏时遇到了类似的问题, .env.dusk.testing 中的 APP_KEY 与 .env 中的 APP_KEY 不匹配

【讨论】:

【参考方案6】:

本质上,您的签名不匹配,因为您通过 \Illuminate\Support\Facades\URL::signedRoute 生成的 URL 已被您的中间件更改,这意味着在检查 $request->hasValidSignature() 时返回 false。

我遇到了类似的问题,SendGrid 将 UTM 跟踪查询字符串添加到我的电子邮件 (&utm_campaign=website&utm_source=sendgrid.com&utm_medium=email) 中的 URL,这改变了 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);

【讨论】:

【参考方案7】:

试试下面的代码:

class TrustProxies extends Middleware

    protected $proxies = '*';
    protected $headers = Request::HEADER_X_FORWARDED_ALL;

【讨论】:

编辑 TrustProxies.php middleware.change protected $proxies 代码为 protected $proxies = '*';这将有助于解决 laravel 上的 403 错误。 @developer_avijit -- 也许尝试将您的评论作为答案的一部分? 具有讽刺意味的是,我最后尝试了这个答案。谢谢它成功了!如果 Laravel 记录了错误,那将会很有用。【参考方案8】:

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

请参阅this answer 了解更多信息。

【讨论】:

【参考方案9】:

在调试完 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);
    

这个中间件在中间件​​组中,所以希望如果将来有人对此进行实验,那么它可以先检查一下。

【讨论】:

我知道这已经发布很久了,但这救了我的命。我永远不会想到这一点,所以谢谢你! @Andreas,没问题,这就是这样做的目的......我记得我很难找出问题所在,但经过大量挖掘后我做到了。

以上是关于Laravel 5.7 签名路由返回 403 无效签名的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 路由模型绑定 - Laravel 5.7

Laravel 5.7 和 Vue 路由模式

Laravel 5.7 使用某些命名空间的资源控制器路由名称是啥?

Laravel 5.7 - 覆盖请求验证类中的 all() 方法以验证路由参数?

在 Laravel 4 中给出 403 Forbidden 错误的单一路线

订阅私人频道时的 Pusher/Laravel 回显错误:订阅频道的身份验证值无效:格式应为“密钥:签名”