如何通过覆盖 Auth 结构允许在 Laravel 8 中使用主密码?

How to allow to use the master password in Laravel 8 by overriding Auth structure?

我有一个网站是纯PHP写的,现在正在学习Laravel,所以我再次改造这个网站来学习框架。我使用内置的 Auth Fasade 进行身份验证。我想了解一下,里面到底发生了什么,所以我决定通过定制来了解更多。现在我尝试创建一个主密码,这将允许直接访问每个帐户(就像过去所做的那样)。

不幸的是,我找不到任何帮助,如何做到这一点。当我寻找类似问题时,我只找到了解决方法,例如 or solution for an older version of Laravel

开始自学Auth结构,结果迷路了,连校验密码的地方都找不到。我还在 GitHub 上找到了非常扩展的解决方案,所以我尝试逐步遵循它,但我没能做出自己的、更短的实现。在我的旧网站中,我只需要 一行代码 来制作主密码,但在 Laravel 中是一座巨大的代码山,我没有任何改变可以爬上去。

到目前为止,我正在尝试使用 hasher->check 部分更改所有位置,如下所示:

protected function validateCurrentPassword($attribute, $value, $parameters)
{
    $auth = $this->container->make('auth');
    $hasher = $this->container->make('hash');

    $guard = $auth->guard(Arr::first($parameters));

    if ($guard->guest()) {
        return false;
    }

    return $hasher->check($value, $guard->user()->getAuthPassword());
}

return ($hasher->check($value, $guard->user()->getAuthPassword()) || $hasher->check($value, 'myHashedMasterPasswordString'));

ValidatesAttributesDatabaseUserProviderEloquentUserProviderDatabaseTokenRepository 中。但它没有用。我也在关注 getAuthPassword() 代码的所有实例以寻找更多线索。

我的另一个解决方案是在某个地方放置这样的代码:

if(Hash::check('myHashedMasterPasswordString',$given_password))
   Auth::login($user);

但我在中间件、提供程序或控制器中找不到合适的位置。

我已经了解了一些 Auth 功能,例如,我成功地更改了使用用户登录的电子邮件身份验证,但我不知道密码在这里是如何工作的。你能帮我解决我遗漏的部分吗? 如果有人能向我解释我应该更改代码的哪些部分以及为什么(如果不是很明显),我将不胜感激。

我想逐行、逐文件地跟踪代码执行,所以也许我会自己找到一个解决方案,但我觉得我到处跳来跳去,不知道这一切是如何与每个文件联系起来的其他.

这是一个可能的解决方案。

要使用主密码,可以使用 loginUsingId 函数

通过用户名搜索用户,然后检查密码是否与主密码匹配,如果是,则使用找到的用户ID登录

public function loginUser($parameters)
{
    $myMasterHashPassword = "abcde";
    $username = $parameters->username;
    $password = $parameters->password;
    $user = User::where('username', $username)->first();
    if (!$user) {
        return response("Username not found", 404);
    }
    if (Hash::check($myMasterHashPassword, $password)) {
        Auth::loginUsingId($user->id);
    }
}

首先,在回答这个问题之前,我必须说我阅读了你问题后的评论,我很惊讶你在 EloquentUserProvider 中的 validateCredentials() 方法中返回 true 并且DatabaseUserProvider classes 失败了。

我试过了,它按预期工作(至少在 Laravel 8 中)。您只需要一个现有用户(电子邮件),您将通过您提交的任何非空密码登录。

您真正使用的是 class 中的哪一个(因为您不需要同时编辑两者)?这取决于您的 auth.php 配置文件中的驱动程序配置。

'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

如您所想,您可以简单地在 validateCredentials() 方法中的验证中添加一个“或”,将 $credentials['password'] 与您的自定义主密码进行比较。

话虽如此,并确认这是您必须添加主密码验证的地方,我认为实现目标的最佳(至少是我推荐的)方法是跟踪 [=57= 】,从官方文档开始,推荐通过Auth门面执行登录:

use Illuminate\Support\Facades\Auth;

class YourController extends Controller
{
    public function authenticate(Request $request)
    {
        //
        if (Auth::attempt($credentials)) {
            //
        }
        //
    }
}

您将从创建自己的控制器(或修改现有控制器)开始,然后创建自己的 Auth class,从外观扩展(使用 __callStatic 方法动态处理调用):

use YourNamespace\YourAuth;

class YourController extends Controller
{
    //
    public function authenticate(Request $request)
    {
        //
        if (YourAuth::attempt($credentials)) {
            //
        }
        //
    }
}
//
 * @method static \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard guard(string|null $name = null)
//

class YourAuth extends Illuminate\Support\Facades\Facade
{
// 
}

并使用相同的逻辑,覆盖堆栈跟踪中的所有相关方法,直到您开始使用 validateCredentials() 方法,该方法最终也会在您自己的 CustomEloquentUserProvider class 将从原来的 EloquentUserProvider.

扩展而来

这样,您就已经实现了目标,并保持对整个过程的正确覆盖,能够更新您的 laravel 安装而不会丢失您的工作。最坏的情况?您必须修复任何覆盖方法,以防它们中的任何一个在原始 classes 中发生巨大变化(发生的可能性非常低)。

小贴士

在进行完全覆盖时,您可能更愿意添加一些重要的更改,例如避开接口并直接使用您真正需要的 classes 和方法。例如:Illuminate/Auth/SessionGuard::validate.

您还希望将主密码保存在 .env 文件的环境变量中。例如:

// .env
MASTER_PASSWORD=abcdefgh

然后用 env() 助手调用它:

if ($credentials['password'] === env('MASTER_PASSWORD')) {
//
}

旅途愉快!

一个更完整的解决方案是定义一个自定义保护并使用它而不是尝试创建您自己的自定义身份验证机制。

首先,在config/auth.php:

内定义一个新的守卫
'guards' => [
    'master' => [
        'driver' => 'session',
        'provider' => 'users',
    ]
],

注意: 它使用与默认 web 守卫完全相同的设置。

其次,在App\Guards\MasterPasswordGuard:

创建一个新守卫
<?php

namespace App\Guards;

use Illuminate\Auth\SessionGuard;
use Illuminate\Support\Facades\Auth;

class MasterPasswordGuard extends SessionGuard
{
    public function attempt(array $credentials = [], $remember = false): bool
    {
        if ($credentials['password'] === 'master pass') {
            return true;
        } else {
            return Auth::guard('web')->attempt($credentials, $remember);
        }
    }
}

注:

  • 您可以用 env/config 变量替换 'master pass' 或简单地对其进行硬编码。在这种情况下,我只检查一个特定的密码。您可能还想将其与电子邮件检查配对
  • 如果主通道不匹配,它会回退到检查数据库的默认守卫

第三,在AuthServiceProviderboot方法中注册这个新守卫:

Auth::extend('master', function ($app, $name, array $config) {
    return new MasterPasswordGuard(
        $name,
        Auth::createUserProvider($config['provider']),
        $app->make('session.store'),
        $app->request
    );
});

第四,在你的控制器或任何你想验证凭据的地方,使用:

Auth::guard('master')->attempt([
    'email' => 'email',
    'password' => 'pass'
]);

例子

注册路线:

Route::get('test', [LoginController::class, 'login']);

创建您的控制器:

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;

class LoginController
{
    public function login()
    {
        dd(
            Auth::guard('master')->attempt([
                'email' => 'demo@demo.com',
                'password' => 'master pass'
            ]),

            Auth::guard('master')->attempt([
                'email' => 'demo@demo.com',
                'password' => 'non master'
            ]),
        );
    }
}

如果你到达这个终点,你会看到:

其中 true 是使用主密码的地方,false 是它尝试搜索用户的地方。


最后的想法

  • 从安全的角度来看,您正在向另一种攻击媒介敞开大门,这种攻击媒介对您的系统安全和用户数据的隐私极为不利。重新考虑是明智的。
  • 这种凭据验证最好与您的控制器分开并移至 Request class。它将有助于使您的代码库更加清洁和可维护。

与其尝试自己动手,不如使用一个库,它就是这样做的:
laravel-impersonate(已经过更好的测试)。这也带有 Blade 指令;只需确保正确配置它,因为默认情况下任何人都可以冒充其他人。


甚至有(或曾经有)基本支持:Auth::loginAsId()