Laravel 5.6 实现 URL 自动登陆

自动登录

当用户回到你的应用程序时,自动登录是一个很好的方法让用户和你的应用保持紧密联系。如果你能防止他们从可能忘记密码的认证流程中分散注意力,或者甚至通过他们加入你网站的电子邮件地址来访问你的网站,那么他们可以继续做他们想做的事情,用户也会为此感到高兴。

我使用过的最常见的用例就是客户通知。 无论是电子邮件还是文本,如果你发送一个能够重新回到你网站的私密链接,你可以将其设置为重新登录到他们的账号 (如果他们还没有登录)。

旧的方式

我创建了一个 watson/autologin 包去实现这个特性-这个包用来生成一个用户自动登录的链接。这个包依赖一个数据库的表,表里面存储着还没到期的 URL 的 token 以及重定向的链接和要登录的用户信息。

新的方式

在 Laravel 5.6.12 中,有一个 称作为 signed URLS 的新的不错的特性 ,这个自带签名认证 URL 可以很简单的实现 URL 自动登录并且不需要依赖数据的存储。

让我们来看一个关于如何实现 URL 自动登录的简单例子。首先,我们先要创建一个用户自动登录的路由,并在这个路由上加上 signed 中间件。如果 URL 的签名不匹配,这个中间件将会抛出一个错误。在下面的例子中,我们让用户认证通过,登录,最后重定向到 home 。

use App\User;
use Illuminate\Support\Facades\Auth;

Route::get('/autologin/{user}, function (User $user) {
  Auth::login($user);

  return redirect()->home();
})->name('autologin')->middleware('signed');

生成 URL 也很简单,你可以使用 URL 的门面模式去生成,跟其他 URL 的生成方式是一样的。

use App\User;
use Illuminate\Support\Facades\URL;

$user = User::first();

$url = URL::signedRoute('autologin', ['user' => $user]);

上面的代码就会生成你所期望的 URL ,只是在 URL 上会带有一个很长的“随机”签名作为查询参数。就像这样子 /autologin/1?signature=someReallyLongString 。当 Laravel 去解析这个路由的时候, signed 中间件会先去查看并且验证这个签名是否有效,只有中间件这里验证通过了,程序才会继续往下面执行。

如果你想设置自动登录链接的有效期,其实这个功能也是开箱即用的。比如说你可以拥有一个自动登录的链接,这个链接一天以后将会失效。

但是要注意一点,如果你设置了自动登录链接的有效期,就要去考虑添加一些异常处理 Illuminate\Routing\Exceptions\InvalidSignatureException (当收到不合法的签名时,将会抛出异常)并且要通知你的用户,让他们知道是这个链接过期了而不是直接返回一个 状态码为 500 错误响应。

use App\User;
use Illuminate\Support\Facades\URL;

$user = User::first();

$url = URL::temporarySignedRoute(
  'autologin',
  now()->addDays(1),
  ['user' => $user]
);

工作原理?

实际上非常值得潜下心来看一看,它的底层是如何工作的,因为它的实现非常简单。让我们看这个 signedRoute 方法,忽略其它内容直接看最后一行。

/**
 * 为命名路由创建一个签名路由URL。
 *
 * @param  string  $name
 * @param  array  $parameters
 * @param  \DateTimeInterface|int  $expiration
 * @return string
 */
public function signedRoute($name, $parameters = [], $expiration = null)
{
    $parameters = $this->formatParameters($parameters);

    if ($expiration) {
        $parameters = $parameters + ['expires' => $this->availableAt($expiration)];
    }

    $key = call_user_func($this->keyResolver);

    return $this->route($name, $parameters + [
        'signature' => hash_hmac('sha256', $this->route($name, $parameters), $key),
    ]);
}

这个方法真正地返回你想要的 URL,并且带有附加查询参数 signature ,查询参数是通过把路由本身和应用的加密秘钥以 SHA-256 算法哈希加密处理的方式获得的。

这个 APP_KEY 是你在 .env 文件中设置的参数,用来内部加密。除了你之外没有人知道 APP_KEY 的值,所以其他人不可能给你的路由通过 SHA 加密生成一个合法的签名。

很不错-但接下来会发生什么?

如果我们去查看下 Illuminate\Routing\Middleware\ValidateSignature 这个中间件,里面没有很多的代码,但是它很简洁和清晰。

if ($request->hasValidSignature()) {
    return $next($request);
}

throw new InvalidSignatureException;

这个中间件会要求确认请求的签名是否合法,如果是合法的,则调用堆栈中的下一个中间件。如果不是合法的, 则这个请求不通过,并且抛出一个 InvalidSignatureException 的异常,阻止黑客去攻击这些看起来很招摇的 URL 。

.

最后让我们来看下 hasValidSignature 代码实现,代码不少,不过我们只需要专注几个重点即可。

/**
 * 检测请求是否有正确的签名
 *
 * @param  \Illuminate\Http\Request  $request
 * @return bool
 */
public function hasValidSignature(Request $request)
{
    $original = rtrim($request->url().'?'.http_build_query(
        Arr::except($request->query(), 'signature')
    ), '?');

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

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

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

首先重建去除了签名参数的路由,下一步对其进行 SHA-256 哈希,并将其值与 Laravel 生成的哈希签名做验证,如果哈希相等就算通过。

你可以看到,此函数甚至检查 expires 参数,这是为  temporarySignedRoute 的逻辑准备的。

感谢框架作者 Taylor 新增了这个简单的而优雅的功能。
另外

是否觉得奇怪,请求时中间件调用了 hasValidSignature() 方法,它实际属于 Illuminate\Routing\UrlGenerator? 这实际证明,它在 Laravel 的 FoundationServiceProvider 服务中被注册为宏。

Request::macro('hasValidSignature', function () {
    return URL::hasValidSignature($this);
});

不错的收获.

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://www.neontsunami.com/posts/autolo...

译文地址:https://learnku.com/laravel/t/13018/lara...

本帖已被设为精华帖!
本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 4
Summer

此方案用来实现如登录电脑版 QQ,点击 QQ 邮箱或者 QQ 空间 icon ,浏览器打开相关页面并且直接是登录状态。

5年前 评论
Summer

此方案用来实现如登录电脑版 QQ,点击 QQ 邮箱或者 QQ 空间 icon ,浏览器打开相关页面并且直接是登录状态。

5年前 评论

@Summer 做成二维码手机扫一扫还能在手机上或者App上登录。加个长连接+二维码还能在手机App上确认登录Web页。

5年前 评论

@Summer 看了这篇文章才发现 Signed URLs 在5.6的文档中没有。文档翻译内容缺失啊,而且好像不只是这一篇的问题,应该是5.6后续版本升级新增的内容在中文文档中没有同步新增,建议翻译对着官方文档再校对一下。

5年前 评论

@雪风 支持,这么好的功能,希望能有中文翻译

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!