Заставить Laravel войти в систему с использованием устаревшей аутентификации

Я пытаюсь медленно интегрировать 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();

После этого я получаю файл cookie laravel_session с содержимым на клиенте. Во время запроса на вход после выполнения приведенного выше кода, если я dd(Auth::user()), то я получаю пользователя, с которым я только что вошел в систему. Однако при последующих запросах Auth::user() и $this->request->user() оба возвращают null во всех контекстах.

Как я могу принудительно активировать пользовательский сеанс Laravel без фактической аутентификации, который будет сохраняться между запросами?


Конечным результатом является то, что Laravel будет работать как «подприложение» под устаревшим приложением, в то время как существующие функции будут добавляться одна за другой, так что обе будут существовать в течение определенного периода времени, пока все функции не будут реализованы в Laravel, и он заменит существующее приложение в полном объеме. Если имеет больше смысла попытаться взять на себя устаревшую аутентификацию с помощью Laravel, а не наоборот, я открыт для этого, но я бы предпочел избежать необходимости изменять базовую таблицу пользователей (устаревшая аутентификация выполняется через LDAP, поэтому локально нет паролей, нет Remember_token, но его достаточно легко добавить, если мне нужно). Я просто ищу кратчайший путь с наименьшими усилиями/головной болью.


person Jeff Lambert    schedule 23.03.2017    source источник
comment
Пожалуйста, добавьте ответ, когда решите проблему. Хороший вопрос, хороший вопрос, палец вверх.   -  person Gammer    schedule 28.03.2017


Ответы (4)


Это немного сложно, потому что Laravel использует зашифрованные файлы cookie, которые обрабатываются промежуточным программным обеспечением EncryptCookies. Вы можете заставить свой код работать, если отключите его, но я бы не рекомендовал его.

Решение состоит в том, чтобы использовать промежуточное ПО Laravel EncryptCookies для расшифровки запроса и последующего шифрования ответа. Это сделает сеанс, созданный вашей устаревшей аутентификацией, доступным для чтения Laravel.

Учтите, что это называется файлом login.php, которому требуется $user_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();
person Amr El-Naggar    schedule 29.03.2017
comment
Кажется, это работает и намного больше соответствует тому, как я хочу подойти к этому, спасибо. Я даже не подумал о шифровании файла cookie! Я собираюсь потыкать его в течение часа или двух, и если он все еще работает, я думаю, что этот победитель. - person Jeff Lambert; 29.03.2017
comment
Хорошо @AmrEl-Naggar, рад видеть, что кто-то смог это понять - person Markus Tenghamn; 30.03.2017

Это не ответит на ваш вопрос о Laravel напрямую, но если подумать немного нестандартно, то, чего вы пытаетесь достичь, в принципе, это SSO (единый вход).

Только ради миграции это может быть излишним решением, но если вы планируете перенести больше взаимосвязанных веб-сайтов или разработать приложение, это стоит рассмотреть.

Есть три варианта (как обычно):

  • реализовать свое решение
  • использовать библиотеку, например. bshaffer/oauth2-server-php
  • использовать сторонний сервис

Если речь идет о сторонней библиотеке, я могу порекомендовать сервис Auth0, который может снять с вас бремя аутентификации. В зависимости от количества пользователей это может быть даже бесплатное решение. Я рекомендую это, основываясь на собственном опыте использования сервиса.

Подробнее о системе единого входа

Пример системы единого входа Auth0

person lchachurski    schedule 29.03.2017
comment
Это хорошее наблюдение, спасибо. В конечном итоге мы будем использовать OAuth с использованием laravel/passport, поскольку это официально поддерживается, но в настоящее время мы просто ищем поддержку одного веб-приложения на стороне клиента и пытаемся поставить проект на прочную основу. - person Jeff Lambert; 29.03.2017

Вы не вызываете 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);, во второй файл и запустил его, и дамп показывает, что тот же пользователь все еще вошел в систему. Выход из системы и удаление строки, сохраняющей сеанс, остановит это. Надеюсь, это решение, которое вы искали.

person Markus Tenghamn    schedule 28.03.2017
comment
Это определенно кажется правильным, хотя по какой-то причине я все еще не получаю Auth::user() для возврата чего-либо, кроме нуля, по второму запросу (первый, как у вас, правильно выгружает пользователя, входящего в систему. Я думал, что у меня есть это также по следующему запросу, но потом забыл, что только что издевался над пользователем, чтобы двигаться дальше). Я буду продолжать возиться, чтобы увидеть, есть ли у меня ошибка где-то еще, и если я смогу заставить это работать, награда за вас. Спасибо! - person Jeff Lambert; 28.03.2017
comment
Хм, странно var_dump(Auth::user()); работает для меня на странице входа и на другой странице, которую я создал. Мне нужно идти сейчас, но мне нужно попробовать это на чистой виртуальной машине homestead, чтобы убедиться, что это не то, что я сделал в своем проекте. Интересный вопрос, так как я могу использовать его в будущем. - person Markus Tenghamn; 28.03.2017
comment
Да, я был немного удивлен, что еще не нашел ответа на вопрос, я полагаю, что это уже было реализовано кем-то где-то. - person Jeff Lambert; 28.03.2017

Я бы посоветовал рассмотреть возможность использования 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');
}

Я делал некоторые сомнительные вещи, всегда используя аутентификацию Laravel и LegacyHasher для старых паролей в AuthController:

 /**
 * 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, разбросанных повсюду, но гораздо более управляемым.

Я буду рад помочь с более подробной информацией, если вы решите пойти в этом направлении.

person markdwhite    schedule 29.03.2017
comment
Спасибо за это. Я рассматривал возможность использования Laravel для аутентификации, и действительно, в какой-то момент мне все равно придется использовать эту функцию, поэтому имеет смысл попытаться сделать это заранее. Но с учетом сроков я не думаю, что хочу рисковать заранее и предпочел бы сделать это позже, просто потому, что устаревшее приложение очень разбросано, и я беспокоюсь об интеграции Laravel. аутентификация в нем была бы кошмаром, поскольку мне потенциально пришлось бы касаться сотен файлов. - person Jeff Lambert; 29.03.2017
comment
Я сделал 4 из них для одного и того же клиента, и последнее заняло около 15 минут, чтобы настроить чистую установку Laravel с универсальным маршрутом к OldController. Выше я показал некоторый «незавершенный» код с использованием Auth::user() в old/index.php, но для начала все, что было нужно, это перехватить вывод и вывести его, используя Response:: макрос строка(). Но, по сути, да - возможно, это будет кошмар, но это можно сделать. softwareengineering.stackexchange.com /вопросы/6268/ - person markdwhite; 30.03.2017