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

时间:2018-12-22 01:18:26

标签: php laravel url 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

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

有什么想法吗?预先感谢

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

我已经做过的事情:

  1. 尝试未签名的路线,效果很好
  2. 尝试不带参数且仅签名的路线
  3. 尝试不设置临时路线而仅进行签名的路线
  4. 将cloudflare的ip设置为受信任的代理
  5. 禁用HTTPS,启用HTTPS

似乎什么都没用,总是得到403无效的签名页

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

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

很奇怪,因为我想为用户创建一个临时链接以供用户下载内容,如果他们登录到我的Laravel应用中,他们将收到此403错误消息...:(

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

我尝试在全新安装的laravel中运行并且完美运行,因此它来自我的主要Laravel应用,还尝试将每个作曲者依赖项安装到Laravel的全新安装中,无论用户登录状态如何,它仍然可以完美运行,因此这与我的依赖关系没有冲突。

9 个答案:

答案 0 :(得分:1)

我只是遇到了这个问题,结果证明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可能与此有关。

答案 1 :(得分:1)

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

我使用的是 Laravel 8+

答案 2 :(得分:0)

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

该中间件位于中间件组中,所以希望这是一个问题,如果将来有人进行实验,那么它可以首先进行检查。

答案 3 :(得分:0)

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

有关更多信息,请参见this answer

答案 4 :(得分:0)

尝试以下代码:

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

答案 7 :(得分:0)

我遇到了同样的问题,并且发疯了,直到偶然发现@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可行! 谢谢大家

答案 8 :(得分:0)

了解 LARAVEL 电子邮件验证方式

了解验证方式可以帮助您轻松解决此错误。

laravel 使用 URL::temporarySignedRoute() 方法制作临时签名网址,

此方法在位于的 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).env('APP_URL') 制作网址。

所以如果发送到email的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 不同。
相关问题