Laravel 测试 BEFORE 组中间件中的 URL 参数



我正在使用中间件来检查是否存在来自另一个站点的登录令牌。 如果登录令牌存在并且用户尚未登录,我想使用该令牌登录用户并将其发送到其预期页面。如果他们已经登录,我希望它什么都不做。

正如建议的那样,这应该是一个ServiceProvider吗?

这是我的中间件:

<?php
namespace AppHttpMiddleware;
use Session;
use Closure;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;
use AppHttpControllersAuthLoginController;
class CheckRepLoginToken
{
/**
* Handle an incoming request.
*
* @param  IlluminateHttpRequest  $request
* @param  Closure  $next
* @return mixed
*/
public function handle(Request $request, Closure $next){
$loginToken = $request->repLoginToken;
$goto = '/'.$request->path();
if(isset($loginToken) && Auth::guest()){
(new LoginController)->login($loginToken,$goto);
}

return $next($request);
}
}

问题是我需要在$middlewareGroups$routeMiddleware之前运行它,因此如果Auth::guest()为 true,但令牌存在,则不会将用户发送到登录屏幕。

我目前在Kernelprotected $middleware部分有中间件,每个人似乎都是"客人",无论他们是否登录。

这是内核文件:

<?php
namespace AppHttp;
use IlluminateFoundationHttpKernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
// AppHttpMiddlewareTrustHosts::class,
AppHttpMiddlewareTrustProxies::class,
FruitcakeCorsHandleCors::class,
AppHttpMiddlewarePreventRequestsDuringMaintenance::class,
IlluminateFoundationHttpMiddlewareValidatePostSize::class,
AppHttpMiddlewareTrimStrings::class,
IlluminateFoundationHttpMiddlewareConvertEmptyStringsToNull::class,
AppHttpMiddlewareCheckRepLoginToken::class,
// 'checkStatus' => AppHttpMiddlewareCheckStatus::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
AppHttpMiddlewareEncryptCookies::class,
IlluminateCookieMiddlewareAddQueuedCookiesToResponse::class,
IlluminateSessionMiddlewareStartSession::class,
// IlluminateSessionMiddlewareAuthenticateSession::class,
IlluminateViewMiddlewareShareErrorsFromSession::class,
AppHttpMiddlewareVerifyCsrfToken::class,
IlluminateRoutingMiddlewareSubstituteBindings::class,
],
'api' => [
// LaravelSanctumHttpMiddlewareEnsureFrontendRequestsAreStateful::class,
'throttle:api',
IlluminateRoutingMiddlewareSubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => AppHttpMiddlewareAuthenticate::class,
'auth.basic' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class,
'cache.headers' => IlluminateHttpMiddlewareSetCacheHeaders::class,
'can' => IlluminateAuthMiddlewareAuthorize::class,
'guest' => AppHttpMiddlewareRedirectIfAuthenticated::class,
'password.confirm' => IlluminateAuthMiddlewareRequirePassword::class,
'signed' => IlluminateRoutingMiddlewareValidateSignature::class,
'throttle' => IlluminateRoutingMiddlewareThrottleRequests::class,
'verified' => IlluminateAuthMiddlewareEnsureEmailIsVerified::class,
];
}

如何在不弄乱当前身份验证的情况下实现所需的结果?

起初,中间件似乎是一次性登录令牌功能的正确工具,但是如果不了解Laravel的请求管道,实现可能会很棘手。在本答案的最后,我们将介绍一种使用自定义身份验证功能的简单替代方法。

对于来自浏览器的传统网络请求,Laravel的身份验证服务依赖于由cookie标识的会话的存在。问题的CheckRepLoginToken中间件在Kernel.php的全局中间件数组中声明,这些处理程序在路由中间件之前执行,其中包括'web'组中的StartSession中间件。

由于StartSession中间件初始化请求的会话状态,因此全局CheckRepLoginToken中间件的身份验证上下文尚不可用。从全局中间件调用Auth::guest()将始终返回问题中显示的配置的true。我不确定LoginController::login()方法在您的特定项目中做了什么,但我想通过尝试从全局中间件调用该方法来设置的身份验证状态设置可能会在标准会话和身份验证中间件之后运行时消失。

根据LoginController::login()方法的作用,将CheckRepLoginToken中间件的声明移动到'web'组中StartSession以下可能就足够了。顺便说一句,有些人可能会认为实例化控制器以直接调用该方法是一种不好的做法。我们可以在没有太多代码的情况下实现类似的结果:

public function handle(Request $request, Closure $next)
{
if ($request->has('repLoginToken') && Auth::guest()) {
$user = // ...try to fetch a user with $request->repLoginToken...
if ($user !== null) {
Auth::login($user);
}
}
return $next($request);
}

更完整的解决方案利用了Laravel的可插拔身份验证系统。我们可以用处理令牌的自定义实现来包装 Laravel 的标准身份验证保护。

首先,我们将更新config/auth.php以切换默认'web'防护以使用我们将在下面实现的自定义驱动程序。我们将原始'web'卫士重命名为'session',以便我们稍后可以参考它。

'guards' => [ 
'web' => [
'driver' => 'rep-token', 
],
'session' => [
'driver' => 'session',
'provider' => 'users',
]
],

Laravel 的AuthManager包括一个辅助方法(viaRequest()),该方法简化了使用请求上下文中的数据对用户进行身份验证的Guard的创建,而无需完全实现IlluminateContractsAuthGuard。我们在AuthServiceProvider中的boot()方法中绑定自定义防护.php:

public function boot()
{
Auth::viaRequest('rep-token', function ($request) {
$baseGuard = Auth::guard('session');
if ($request->has('repLoginToken') && $baseGuard->guest()) {
$user = // ...try to fetch a user with $request->repLoginToken...
if ($user !== null) {
$baseGuard->login($user);
}
}
return $baseGuard->user();
});
}

如我们所见,此包装器重用了 Laravel 基于会话的标准身份验证的功能,并处理存在repLoginToken的特殊情况。它不需要任何额外的中间件。

由于这篇文章是公开的,我觉得有义务强调 Mtxz 回答中的一个观点。在设计和实施第三方身份验证方案时要格外小心。通常,任何获取有效令牌的人(包括第三方)都可以完全访问用户帐户。此问题中描述的身份验证流的简单性表明了许多应用程序可能无法接受的漏洞。

尝试命名您的路线并在此处执行以下操作:

Route::get('/login/{loginToken}', 'LoginController@login')->name('login.route');
if (isset($loginToken) && Auth::guest()) {
return redirect()->route('login.route', [
'token' => $loginToken
])      
}

实际上,为了让Laravel知道用户是否登录,请求需要传递身份验证中间件。因此,您的自定义中间件需要在身份验证之后触发。

因此,如果您的中间件需要知道用户是否已登录,则必须首先传递身份验证中间件(也意味着它仅适用于身份验证中间件下的路由或路由组 - 因为我看到身份验证中间件不在您的默认应用程序中间件堆栈中)。

如果身份验证中间件重定向用户,则永远不会调用您的中间件。

覆盖身份验证中间件

由于您的业务与"身份验证相关",因此您可以轻松覆盖正在使用的身份验证中间件。复制或扩展供应商类,将内核中的身份验证中间件类替换为您的类,并在默认中间件业务识别或未登录用户后添加您的自定义业务。

我想您也可以通过创建自定义身份验证保护并使用它而不是默认的,但我认为自定义身份验证中间件更快。

我对你的自定义身份验证业务了解不多,但要小心 URL 中的令牌(可以通过邮件发送,或保存在其他地方),这些令牌应该在延迟和使用后过期。此外,还应防止暴力破解此参数。

最新更新