Ответ 1
Да, с использованием подхода с сохраненными маркерами вам нужно было бы сохранить все сгенерированные токены на случай, если они вернутся в любой момент. Один сохраненный токен терпит неудачу не только для нескольких вкладок/окон браузера, но также для обратной/передовой навигации. Обычно вы хотите управлять потенциальным взрывом хранилища, истекли старые токены (по возрасту и/или количеству жетонов, выпущенных с тех пор).
Другой подход, который позволяет избежать хранения токенов в целом, заключается в выпуске подписанного токена, сгенерированного с использованием секретности на стороне сервера. Затем, когда вы получите маркер назад, вы можете проверить подпись, и если она совпадает, вы знаете, что подписали ее. Например:
// 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 не просачивают токен, который будет работать для этого пользователя навсегда, и значение, которое изменяется на сбрасывает пароль, так что смена пароля делает недействительными существующие токены.