强制 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->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']->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 使用旧版身份验证登录用户的主要内容,如果未能解决你的问题,请参考以下文章