强制 Laravel 使用旧版身份验证登录用户

Posted

技术标签:

【中文标题】强制 Laravel 使用旧版身份验证登录用户【英文标题】:Force Laravel to log in a user using legacy authentication 【发布时间】:2017-08-16 08:36:07 【问题描述】:

我正在尝试将 Laravel 慢慢集成到旧版 php 应用程序中。其中一项任务是在用户登录旧应用程序时自动注册 Laravel 用户会话。我不是在尝试实现 Laravel 身份验证,我真的只是想利用现有功能并强制特定用户登录而不检查凭据。到目前为止,我所拥有的都是从我发现的其他人的黑客中拼凑而成的:

// Laravel authentication hook - Boostrap application
require_once getcwd() . '/../laravel/bootstrap/autoload.php';
$app = require_once getcwd() . '/../laravel/bootstrap/app.php';
$kernel = $app->make('Illuminate\Contracts\Console\Kernel');
$kernel->bootstrap();
$app->boot(); 

// Start Laravel session
$request = Illuminate\Http\Request::capture();
$response = $app->make('Symfony\Component\HttpFoundation\Response');
$startSession = new Illuminate\Session\Middleware\StartSession($app['session']);
// Associate server session with the authenticating user id
// I have also tried loading user model instance and then $app['auth']->login($user)
$app['auth']->loginUsingId($user_id);

$app['session']->driver()->start();
// Terminate middleware response chain with naked response
$response = $startSession->handle($request, function() use($response) 
    return $response; // This response will have session cookie attached to it
);

$response->send();

在此之后,我得到一个laravel_session cookie,它在客户端上有内容。在上面的代码执行后的登录请求期间,如果我 dd(Auth::user()) 然后我得到我刚刚登录的用户。但是,在随后的请求中,Auth::user()$this->request->user() 在所有上下文中都返回 null

如何强制一个活跃的 Laravel 用户会话而无需实际验证,这将在请求中持续存在?


最终的结果是,Laravel 将作为遗留应用程序下的“子应用程序”运行,而现有功能被一个一个拉入,这样两者都将存在一段时间,直到所有功能都在 Laravel 中实现,并且它将完全取代现有的应用程序。如果尝试使用 Laravel 接管旧版身份验证而不是其他方式更有意义,我对此持开放态度,但我宁愿避免更改底层用户表(旧版身份验证是通过 LDAP 进行的,所以本地没有密码,也没有 remember_token,但如果我 的话,这很容易添加)。我真的只是在寻找最少的努力/头痛的最短路径。

【问题讨论】:

解决问题时请添加答案。问得好,问得好,点赞。 【参考方案1】:

这有点棘手,因为 Laravel 使用由 EncryptCookies 中间件处理的加密 cookie。如果你禁用它,你可以让你的代码工作,但我不推荐它。

解决方案是使用 Laravel EncryptCookies 中间件对请求进行解密,然后对响应进行加密。这将使 Laravel 可以读取由旧身份验证创建的会话。

考虑一下这个叫做 login.php 的文件,它需要 $user_id 来通过 id 将用户登录到 laravel。

<?php

require_once getcwd() . '/../laravel/bootstrap/autoload.php';
$app = require_once getcwd() . '/../laravel/bootstrap/app.php';
$kernel = $app->make('Illuminate\Contracts\Console\Kernel');
$kernel->bootstrap();
$app->boot();

$request = Illuminate\Http\Request::capture();
$response = $app->make('Symfony\Component\HttpFoundation\Response');
$startSession = new Illuminate\Session\Middleware\StartSession($app['session']);
$encryptCookies = new App\Http\Middleware\EncryptCookies($app['encrypter']);

$app['session']->driver()->start();

$app['auth']->loginUsingId($user_id);

$response = $encryptCookies->handle($request, function ($request) use ($startSession, $response)

    return $startSession->handle($request, function () use ($response)
    
        return $response;
    );
);

$app['session']->driver()->save();

var_dump($app['auth']->user());

$response->send();

$encryptCookies-&gt;handle()的闭包内,你可以读取解密后的请求,在这里你可以修改会话。当你返回响应时,它会再次被加密,然后你可以将它发送到浏览器。

要读取另一个文件中的会话,您只需执行以下操作:

<?php

require_once getcwd() . '/../laravel/bootstrap/autoload.php';
$app = require_once getcwd() . '/../laravel/bootstrap/app.php';
$kernel = $app->make('Illuminate\Contracts\Console\Kernel');
$kernel->bootstrap();
$app->boot();

$request = Illuminate\Http\Request::capture();
$response = $app->make('Symfony\Component\HttpFoundation\Response');
$startSession = new Illuminate\Session\Middleware\StartSession($app['session']);
$encryptCookies = new App\Http\Middleware\EncryptCookies($app['encrypter']);

$response = $encryptCookies->handle($request, function ($request) use ($startSession, $response)

    return $startSession->handle($request, function () use ($response)
    
        return $response;
    );
);

var_dump($app['auth']->user());

$response->send();

【讨论】:

这似乎可行,并且更符合我想要的处理方式,谢谢。我什至没有考虑加密cookie!我要戳一两个小时,如果它仍然有效,我认为这个是赢家。 很好 @AmrEl-Naggar ,很高兴看到有人能够解决这个问题【参考方案2】:

这不会直接回答您的 Laravel 问题,但跳出框框思考一下,原则上您要实现的是 SSO(单点登录)。

仅出于迁移的目的,这可能是一个矫枉过正的解决方案,但如果您计划迁移更多互连的网站或开发应用程序,则值得考虑。

一共有三个选项(和往常一样):

实施您的解决方案 使用库,例如​​。 bshaffer/oauth2-server-php 使用第三方服务

如果涉及到 3rd 方库,我可以推荐 Auth0 服务,它可以减轻您的身份验证负担。根据用户数量,它甚至可能作为免费解决方案提供。我根据自己的服务经验推荐这个。

More about SSO

Auth0 SSO example

【讨论】:

这是一个很好的观察,谢谢。最终,我们将使用 laravel/passport 来使用 OAuth,因为它是官方支持的,但目前我们只是希望支持单个客户端 Web 应用程序,并试图让该项目站稳脚跟。【参考方案3】:

您没有在 Session 上调用 save(),这就是您的会话无法持续的原因。我修改了你代码的最后几行,如下所示:

// Laravel authentication hook - Boostrap application
require_once getcwd() . '/../laravel/bootstrap/autoload.php';
$app = require_once getcwd() . '/../laravel/bootstrap/app.php';
$kernel = $app->make('Illuminate\Contracts\Console\Kernel');
$kernel->bootstrap();
$app->boot(); 

// Start Laravel session
$request = Illuminate\Http\Request::capture();
$response = $app->make('Symfony\Component\HttpFoundation\Response');
$startSession = new Illuminate\Session\Middleware\StartSession($app['session']);
// Associate server session with the authenticating user id
// I have also tried loading user model instance and then $app['auth']->login($user)
$app['auth']->loginUsingId($user_id);

$app['session']->driver()->start();
// Terminate middleware response chain with naked response
$response = $startSession->handle($request, function() use($response) 
    return $response; // This response will have session cookie attached to it
);

 // Added the following two lines
 var_dump($app['auth']->user()); //This is for debugging
 $app['session']->driver()->save();

$response->send();

我复制了这段代码,只删除了$app['auth']-&gt;loginUsingId($user_id); 行,到第二个文件并运行它,转储显示同一用户仍在登录。注销并删除保存会话的行将停止工作。希望这是您正在寻找的解决方案。

【讨论】:

这绝对看起来在正确的轨道上,但无论出于何种原因,我仍然没有让Auth::user() 在第二个请求中返回 null 以外的任何内容(第一个请求确实正确转储了登录用户。我以为我在下一个请求中也有它,但后来忘记了我刚刚模拟了一个用户,以便我可以继续前进)。我会继续摆弄,看看我在其他地方是否有错误,如果我能让它工作,赏金就是你的。谢谢! 嗯,很奇怪 var_dump(Auth::user()); 在登录页面和我创建的其他页面上为我工作。我现在得走了,但我需要在一个干净的宅基地 VM 上尝试它,以确保它不是我在我的项目中所做的事情。有趣的问题,因为我将来可能会用到它。 是的,我有点惊讶我还没有找到它的答案,我想这已经被某个地方的人实现了。【参考方案4】:

我建议值得考虑将 Laravel 作为主应用程序,允许传递到您尚未转换的路线的遗留代码。我用一个巨大的遗留应用程序做到了这一点,它拥有你对 10 年前编写的应用程序所期望的一切,虽然一开始让它们一起工作很痛苦(类似于你遇到的问题,但反过来),当它完成时,我只是删除旧代码,直到什么都没有。使用你的方法,我看不到最终淘汰旧代码时会发生什么。

我最终得到:

/app
  /Console
  /Http
  etc...
  /old
    /lib
    /screens
    /config
    index.php

一个 OldController 的设置是:

/**
 * Catches all routes for old code
 *
 * @codeCoverageIgnore
 * @return \Illuminate\Http\Response
 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
 */
public function index($all = null)

    ob_start();
    require __DIR__.'/../../old/index.php';
    $html = ob_get_clean();
    return response()->string($html);

旧的/index.php 必须执行以下操作:

// TODO: In place to avoid E_DEPRECATED errors for mysql
error_reporting(E_ALL ^ E_DEPRECATED);
session_start();

// Allow all hardcoded includes in old/ to work
$path = __DIR__;
set_include_path(get_include_path() . PATH_SEPARATOR . $path);

// Set up the global user to suit old code
if (Auth::user()) 
    $myUser = Auth::user()->toArray();
    $myUser['requests'] = getRequestList($myUser['uid']);
    $loggedin = true;
    Log::debug('using Auth::user() id ' . $myUser['uid']);
 else 
    $loggedin = false;
 
// Translate Laravel flashed data to existing message arrays
if (session()->has('login_error_array')) 
    $login_error_array = session()->get('login_error_array');

我在 AuthController 中总是使用 Laravel auth 和 LegacyHasher 作为旧密码,做了一些有问题的事情:

 /**
 * Login
 *
 * @return \Illuminate\Http\RedirectResponse
 */
public function postLogin()

    // Logic is a bit cleaner to follow if we fail late instead of early
    if ($this->hasParams(['login_email', 'login_password'])) 
        // Login using Laravel password
        $credentials = [
            'email' => strtolower(Input::get('login_email')),
            'password' => Input::get('login_password'),
            'active' => ['Y', 'B']
            ];

        // Use the extended user provider to allow using an array of values in the credentials
        $provider = new ExtendedEloquentUserProvider(new BcryptHasher(), config('auth.model'));
        Auth::setProvider($provider);
        if (Auth::attempt($credentials, Input::has('login_remember'))) 

            Log::info(sprintf('successful login from: %s', $credentials['email']));

            return redirect('/');
        

        // Try with legacy password
        $provider = new ExtendedEloquentUserProvider(new LegacyHasher(), config('auth.model'));
        Auth::setProvider($provider);
        if (Auth::attempt($credentials, Input::has('login_remember'))) 
            // Save correctly hashed password
            // TODO: Only add this once this is definitely working ok as it messes up the DB for legacy code
            //Auth::user()->password = Hash::make(Input::get('login_password'));
            //Auth::user()->save();
            Log::info(
                sprintf(
                    'legacy password for login %s, id %d',
                    $credentials['email'],
                    Auth::user()->uid
                )
            );

            Log::info(sprintf('successful login from: %s', $credentials['email']));

            return redirect('/');
        
    

    Log::warning(sprintf('login failed for: %s', Input::get('login_email')));
    // TODO: Use flashed errors for legacy compatibility
    session()->flash('login_error_array', ['Wrong Username or Password']);

    return redirect('login.htm')->withInput();

LegacyHasher(是的,确实如此……)

/**
 * Check the given plain value against a hash.
 *
 * @param  string $value
 * @param  string $hashedValue
 * @param  array  $options
 * @return bool
 */
public function check($value, $hashedValue, array $options = array())

    // Unused param
    $options = [];
    return md5($value) == $hashedValue;

还有很多其他的东西超出了这里的范围。但这是可行的,虽然很痛苦。经过一段时间的工作,所有旧代码都被删除了,我有了一个完整的可测试的 Laravel 应用程序。它有数百个 TODO 散布在各处,但更易于管理。

如果您选择朝这个方向发展,我很乐意为您提供更多详细信息。

【讨论】:

谢谢。我考虑过让 Laravel 接管身份验证的路线,而且真的在某个时候我将不得不拉那个功能,所以尝试提前做是有意义的。但是考虑到时间方面的考虑,我不认为我想提前承担这个风险,而宁愿稍后再加入,因为 Legacy 应用程序分布非常很薄,我担心集成 Laravel身份验证将是一场噩梦,因为我可能不得不接触数百个文件。 我现在已经为同一个客户端完成了其中的 4 个,最后一个花了大约 15 分钟来设置一个干净的 Laravel 安装,其中包含到 OldController 的包罗万象的路由。我在 old/index.php 中使用 Auth::user() 在上面展示了一些“正在进行”的代码,但首先需要的是捕获输出并使用 Response::字符串()宏。但本质上,是的——这可能是一场噩梦,但可以做到。 softwareengineering.stackexchange.com/questions/6268/…

以上是关于强制 Laravel 使用旧版身份验证登录用户的主要内容,如果未能解决你的问题,请参考以下文章

Laravel - 登录时返回用户和身份验证详细信息

如何使用 Windows 集成身份验证强制显示登录窗口

Laravel 默认登录身份验证生成意外错误

Laravel身份验证,我无法检索登录用户

如何使用集成身份验证在 asp net mvc 中强制重新身份验证

laravel 4.2 如何检查是不是有用户登录多重身份验证