Laravel 11 периодические ошибки 419 CSRF при входе в систему, сеансы, используемые с RedisPhp

Кемеровские программисты php общаются здесь
Ответить Пред. темаСлед. тема
Anonymous
 Laravel 11 периодические ошибки 419 CSRF при входе в систему, сеансы, используемые с Redis

Сообщение Anonymous »

Я не знаю наверняка, связана ли эта проблема с использованием Redis для сеансов, но это кажется правдоподобным. Эта проблема возникает только в больших масштабах с производственным трафиком и возникает примерно в 1–2% запросов на вход в систему.
После обновления до laravel 11 и переключения наших сеансов на хранение в Redis (AWS Elasticache Serverless ), мы часто получаем сообщения об ошибках 419 при попытке пользователей войти в систему. 419 означает, что токен CSRF не соответствует тому, что хранится в сеансе.
Похоже, что в этом коммите метод регенерации был изменен, чтобы начать восстановление токена CSRF в сеансе при входе в систему. Ранее вызывался только ->migrate(), который не должен генерировать новый токен CSRF.
Признак AuthenticatesUsers sendLoginResponse вызывает этот метод восстановления.
Я добавлено ведение журнала в структуру laravel, чтобы попытаться отладить проблему, код, который я изменил, приведен ниже. Судя по журналам, токен CSRF генерируется при загрузке страницы входа в систему, а затем восстанавливается, когда они отправляют запрос POST для входа в систему. Затем он сравнивает токен CSRF, отправленный с токеном, только что созданным с помощью запроса, что приводит к несоответствию.
В примере ниже:
16:42:08: Пользователь попадает на страницу входа, и запускается сеанс, который генерирует токен CSRF для сеанса, который передается странице.
16:42:13: пользователь отправляет сообщения на маршрут входа в систему, вызывается метод regenerate и генерируется новый токен CSRF. Затем можно увидеть, что сравнение CSRF не удалось, поскольку он считает, что действительным токеном CSRF является тот, который был только что сгенерирован в 16:42:13, а не токен CSRF, который был сгенерирован в 16:42:08.
* * - - [31/Dec/2024:16:42:08 +0000] "GET /admin/login HTTP/1.1" 200 30400 "https://*/admin/grid/day/2024-12-29" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15"

[2024-12-31 16:42:08] .INFO: SESSION DEBUG: start: regenerateToken {"session_id":"GKkRGO4gXaHgv97SNAGwU4IBh528n6aQ3lXNMux6","csrf_token":"fzpmkW7zrN3vVIcZHwag2f4VrqZeqXwPV01Wslv8","ip_address":"*","session_cookie":null}

[2024-12-31 16:42:12] .INFO: SESSION DEBUG: regenerate: regenerateToken {"session_id":"V1tl039u7Zko2FNKnYBmnktpkpjWvYWnbinQ7p10","csrf_token":"uMGctIR0qgYT50LJSM5VRjvMeN6nRU5ZwP143uF2","ip_address":"*","session_cookie":"GKkRGO4gXaHgv97SNAGwU4IBh528n6aQ3lXNMux6"}

* * - - [31/Dec/2024:16:42:13 +0000] "POST /admin/login HTTP/1.1" 419 67720 "https://*/admin/login" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15"

[2024-12-31 16:42:13] .INFO: SESSION DEBUG: Session ID: V1tl039u7Zko2FNKnYBmnktpkpjWvYWnbinQ7p10 Hash does not match: uMGctIR0qgYT50LJSM5VRjvMeN6nRU5ZwP143uF2 vs fzpmkW7zrN3vVIcZHwag2f4VrqZeqXwPV01Wslv8

Чтобы попытаться исправить это, я перезаписал sendLoginResponse, чтобы вызывать ->migrate() вместо ->regenerate(), который должен был восстановить предыдущее поведение laravel, когда новый Токен CSRF не генерируется при входе в систему. Проблема с этим изменением все еще наблюдается. В этом случае, как ни странно, кажется, что _token не установлен в сеансе, хотя он явно был установлен всего несколько секунд назад. См. примечание ниже: «Для запуска приведенного ниже кода не требуется устанавливать токен».
Пользователь, который попытался войти в систему ниже, является сотрудником, и я подтвердил, что он файлы cookie не были отключены, что в любом случае можно увидеть, поскольку файл cookie сеанса во втором запросе совпадает с файлом cookie первого.
16:03:43:< /strong> Пользователь загружает сайт, который генерирует сеанс.
16:03:47:
Пользователь пытается войти в систему, и в этом запросе запускается новый сеанс/генерируется токен CSRF. Система считает, что это действительный токен CSRF сеанса, и сравнивает его с токеном CSRF в 16:03:43, что приводит к несоответствию.
* * - - [07/Jan/2025:16:03:43 +0000] "GET / HTTP/1.1" 200 26101 "https://*/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"

[2025-01-07 16:03:43] .INFO: SESSION DEBUG: start: regenerateToken {"session_id":"SSJBXTCoydtRHdDhBBH224SQBYqM5PvrGMiCRzx1","csrf_token":"jhZ6BZ2Vo5212jo4EJo6qEkF1wqHjwDHAT2zVh19","ip_address":"*","session_cookie":"SSJBXTCoydtRHdDhBBH224SQBYqM5PvrGMiCRzx1"}

* * - - [07/Jan/2025:16:03:44 +0000] "GET /login HTTP/1.1" 200 23666 "https://*/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"

[2025-01-07 16:03:47] .INFO: SESSION DEBUG: start: regenerateToken {"session_id":"SSJBXTCoydtRHdDhBBH224SQBYqM5PvrGMiCRzx1","csrf_token":"zvn0N1iXxY0eIvC1u6QGbQC1h3XSmxac027GVQBp","ip_address":"*","session_cookie":"SSJBXTCoydtRHdDhBBH224SQBYqM5PvrGMiCRzx1"}

[2025-01-07 16:03:47] .INFO: SESSION DEBUG: Session ID: SSJBXTCoydtRHdDhBBH224SQBYqM5PvrGMiCRzx1 Hash does not match: zvn0N1iXxY0eIvC1u6QGbQC1h3XSmxac027GVQBp vs jhZ6BZ2Vo5212jo4EJo6qEkF1wqHjwDHAT2zVh19

* * - - [07/Jan/2025:16:03:47 +0000] "POST /login HTTP/1.1" 419 67713 "https://*/login" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"

Изменения кода для ведения журнала:
app/Http/Middleware/VerifyCsrfToken.php
namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
protected function tokensMatch($request)
{
$token = $this->getTokenFromRequest($request);

$is_session_token_string = is_string($request->session()->token());
$is_token_string = is_string($token);
$hash_equals = hash_equals($request->session()->token(), $token);

if (!$is_session_token_string) {
\Log::info('SESSION DEBUG: Session token stored in session is not a string: ' . $request->session()->token());
}

if (!$is_token_string) {
\Log::info('SESSION DEBUG: Token from request is not a string: ' . $token);
}

if (!$hash_equals) {
\Log::info('SESSION DEBUG: Session ID: ' . $request->session()->getId() . ' Hash does not match: ' . $request->session()->token() . ' vs ' . $token);
}

return $is_session_token_string && $is_token_string && $hash_equals;
}
}

Сессия/Store.php:
/**
* Generate a new session identifier.
*
* @param bool $destroy
* @return bool
*/
public function regenerate($destroy = false)
{
return tap($this->migrate($destroy), function () {
$this->regenerateToken();

$ipAddress = Request::header('CF-Connecting-IP', Request::ip());
$sessionId = $this->getId();

$sessionCookieName = config('session.cookie'); // Get session cookie name from config
$sessionCookieValue = Request::cookie($sessionCookieName);

Log::info("SESSION DEBUG: regenerate: regenerateToken", [
'session_id' => $sessionId,
'csrf_token' => $this->token(),
'ip_address' => $ipAddress,
'session_cookie' => $sessionCookieValue,
]);
});
}

/**
* Start the session, reading the data from a handler.
*
* @return bool
*/
public function start()
{
$this->loadSession();

if (! $this->has('_token')) { // regenerateToken();

$ipAddress = Request::header('CF-Connecting-IP', Request::ip());
$sessionId = $this->getId();

$sessionCookieName = config('session.cookie'); // Get session cookie name from config
$sessionCookieValue = Request::cookie($sessionCookieName);

Log::info("SESSION DEBUG: start: regenerateToken", [
'session_id' => $sessionId,
'csrf_token' => $this->token(),
'ip_address' => $ipAddress,
'session_cookie' => $sessionCookieValue,
]);
}

return $this->started = true;
}


Подробнее здесь: https://stackoverflow.com/questions/793 ... with-redis
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Php»