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 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
高认可度评论:
此方案用来实现如登录电脑版 QQ,点击 QQ 邮箱或者 QQ 空间 icon ,浏览器打开相关页面并且直接是登录状态。
此方案用来实现如登录电脑版 QQ,点击 QQ 邮箱或者 QQ 空间 icon ,浏览器打开相关页面并且直接是登录状态。
@Summer 做成二维码手机扫一扫还能在手机上或者App上登录。加个长连接+二维码还能在手机App上确认登录Web页。
@Summer 看了这篇文章才发现 Signed URLs 在5.6的文档中没有。文档翻译内容缺失啊,而且好像不只是这一篇的问题,应该是5.6后续版本升级新增的内容在中文文档中没有同步新增,建议翻译对着官方文档再校对一下。
@雪风 支持,这么好的功能,希望能有中文翻译