Да, при использовании подхода с сохраненными токенами вам нужно будет сохранить все сгенерированные токены на случай, если они вернутся в любой момент. Один сохраненный токен не работает не только для нескольких вкладок / окон браузера, но и для навигации назад / вперед. Как правило, вы хотите управлять потенциальным взрывом хранилища путем истечения срока действия старых токенов (по возрасту и / или количеству выданных токенов с тех пор).
Другим подходом, который полностью избегает хранения токенов, является выдача подписанного токена, сгенерированного с использованием секрета на стороне сервера. Затем, когда вы получите токен обратно, вы можете проверить подпись и, если она совпадает, вы знаете, что подписали ее. Например:
// Only the server knows this string. Make it up randomly and keep it in deployment-specific
// settings, in an include file safely outside the webroot
//
$secret= 'qw9pDr$wEyq%^ynrUi2cNi3';
...
// Issue a signed token
//
$token= dechex(mt_rand());
$hash= hash_hmac('sha1', $token, $secret);
$signed= $token.'-'.$hash;
<input type="hidden" name="formkey" value="<?php echo htmlspecialchars($signed); ?>">
...
// Check a token was signed by us, on the way back in
//
$isok= FALSE;
$parts= explode('-', $_POST['formkey']);
if (count($parts)===2) {
list($token, $hash)= $parts;
if ($hash===hash_hmac('sha1', $token, $secret))
$isok= TRUE;
}
При этом, если вы получаете токен с соответствующей подписью, вы знаете, что сгенерировали его. Само по себе это не сильно помогает, но тогда вы можете поместить в токен дополнительные вещи, кроме случайности, например, идентификатор пользователя:
$token= dechex($user->id).'.'.dechex(mt_rand())
...
if ($hash===hash_hmac('sha1', $token, $secret)) {
$userid= hexdec(explode('.', $token)[0]);
if ($userid===$user->id)
$isok= TRUE
Теперь каждая отправка формы должна быть авторизована тем же пользователем, который взял форму, что в значительной степени побеждает CSRF.
Еще одна вещь, которую стоит добавить в токен, это время истечения, так что кратковременный компромисс клиента или атака MitM не пропускают токен, который будет работать для этого пользователя вечно, и изменяют значение пароль сбрасывается, поэтому изменение пароля делает недействительными существующие токены.