Перейти к содержимому

Ошибка проверки источника запроса - разбор причин

Форма в админке или действие на сайте не сохраняются, а вместо результата приходит строка «Ошибка проверки источника запроса». Разбираем причины по убыванию частоты, начиная с подписи сеанса.

С чего начать

Сравниваем пришедшую подпись с ожидаемой:

// временно в начале обработчика, до изменяющей данные логики
$sent = $_REQUEST['sessid'] ?? $_SERVER['HTTP_X_BITRIX_CSRF_TOKEN'] ?? '';
var_dump($sent, bitrix_sessid()); // что прислал браузер и чего ждёт сеанс
var_dump(check_bitrix_sessid()); // false - значения разошлись или подписи нет

Эта пара значений делит все причины пополам. Пустая строка слева означает, что подпись до сервера не дошла, а два разных непустых значения - что сессия сменилась между открытием страницы и отправкой формы.

Смотрим, что лежит в разметке формы:

Окно терминала
curl -s 'https://example.com/personal/profile/' | grep -o 'name="sessid" value="[^"]*"'
# поле есть, а value пустое - страница отдана из кэша композита
# реальное значение туда подставляет клиентский код, и это штатное поведение

Пустое поле подписи в кэшируемом шаблоне не поломка, а следствие правила: серверные переменные в такой шаблон не попадают. Форма без клиентского кода уходит на сервер с пустой подписью и получает отказ.

Ставим подпись в свою форму:

<form method="post" action="/local/tools/save.php">
<?= bitrix_sessid_post() ?> <!-- скрытое поле sessid, первым в форме -->
<input type="text" name="phone">
</form>

Вызов размещают как можно выше: разметка, вставленная перед подписью, способна увести скрытое поле во внешнюю ссылку. Для ссылок с изменяющим действием берут парный вызов bitrix_sessid_get(), но лучше переводить их на метод POST.

Передаём подпись из скрипта:

fetch('/local/tools/save.php', {
method: 'POST',
headers: { 'X-Bitrix-Csrf-Token': BX.message('bitrix_sessid') },
body: new FormData(document.forms.feedback)
});
// BX.bitrix_sessid() возвращает то же значение и годится вместо сообщения

Проверка сверяет с текущей подписью либо параметр запроса, либо этот заголовок. Значение читают на клиенте, а не печатают из PHP: напечатанное однажды застынет в кэше страницы и переживёт смену сессии.

Проверяем, доехала ли cookie сеанса:

$request = \Bitrix\Main\Context::getCurrent()->getRequest();
var_dump($request->getCookie('PHPSESSID')); // null - cookie до сайта не доехала
var_dump($_SERVER['HTTP_HOST'], session_id()); // домен запроса и текущий сеанс
// cookie выдаётся на один домен, и адрес сайта обязан совпасть с настройкой

Cookie сеанса выдаётся ровно на тот домен, который прописан в настройках главного модуля. После переезда на новый адрес старое значение этого поля отсекает cookie, и сверять подпись оказывается уже не с чем.

Смотрим, где сайт хранит сессии:

Окно терминала
grep -A6 "'session'" bitrix/.settings.php # type: file, redis, memcache, database
php -i | grep -E 'session.save_path|session.cookie_domain'
# при нескольких веб-серверах хранение в файлах теряет сессию на соседней ноде

Хранилище сессий объясняет случаи, где отказ приходит через раз. Запрос, ушедший на соседнюю ноду, своей сессии там не находит, и подпись отклоняется без всякой связи с самой формой.

Причины

  1. Своя форма или свой запрос уходят без подписи примерно 30% случаев

    ПризнакОтказ приходит только на самодельной форме или на своём файле обработчика.

    ПроверкаСмотрим разметку формы и тело запроса: есть ли поле подписи или заголовок с ней.

    Что делатьСтавим подпись штатным вызовом в начало формы, а в запрос из скрипта - заголовком.

  2. Подпись пришла пустой из кэшируемой области примерно 22% случаев

    ПризнакВ исходном коде страницы скрытое поле подписи есть, но значение у него пустое.

    ПроверкаЗапрашиваем страницу без браузера и смотрим значение скрытого поля подписи.

    Что делатьБерём значение на клиенте сообщением платформы либо выносим форму в динамическую зону.

  3. Сессия сменилась между открытием и отправкой примерно 18% случаев

    ПризнакФорма пролежала открытой долго либо в соседней вкладке был вход или выход.

    ПроверкаСверяем подпись из разметки открытой страницы с текущим значением подписи сеанса.

    Что делатьПерезагружаем страницу перед отправкой, а долгие формы обновляют подпись запросом.

  4. Сессия не находится на соседнем веб-сервере примерно 15% случаев

    ПризнакОтказ приходит через раз, а повторная отправка того же действия проходит нормально.

    ПроверкаСмотрим настройку хранения сессий и число веб-серверов за балансировщиком.

    Что делатьПереносим сессии в общее хранилище либо привязываем клиента к ноде на балансировщике.

  5. Cookie сеанса не долетает до сервера примерно 10% случаев

    ПризнакОтказ начался после смены домена, переезда на поддомен или ужесточения атрибутов cookie.

    ПроверкаСверяем доменное имя в настройках главного модуля с адресом, по которому открыт сайт.

    Что делатьПриводим доменное имя к рабочему адресу и ослабляем строгий атрибут межсайтовых cookie.

  6. Проактивная защита сменила идентификатор сессии примерно 5% случаев

    ПризнакОтказы пошли сразу после повышения уровня защиты или смены хранилища сессий.

    ПроверкаСмотрим уровень проактивной защиты и журнал событий главного модуля за эти сутки.

    Что делатьЗащиту оставляем включённой, а подпись в формах и запросах подтягиваем на клиенте.

Частые вопросы

Как правильно использовать bitrix_sessid_post и check_bitrix_sessid?

В форму ставят вызов, который создаёт скрытое поле с подписью сеанса. В обработчике проверку выполняют первой строкой: не сошлось - прекращают обработку до изменения данных.

Как получить подпись в JavaScript без библиотек Битрикса?

Значение лежит в скрытом поле формы, и его читают обычным запросом к разметке страницы. С подключённым ядром надёжнее сообщение платформы: оно обновляется вместе с сессией.

Почему в исходном коде страницы поле подписи пустое?

Страница отдана из кэша композита, а серверные переменные в кэшируемый шаблон не попадают. Значение подставляет клиентский код, и это штатное поведение платформы.

Ошибка появилась после смены доменного имени, что смотреть?

Доменное имя в настройках главного модуля и адрес сайта в списке сайтов. Cookie сеанса выдаётся на конкретный домен, и при расхождении подпись до сервера не доезжает.

Та же строка приходит при обмене с 1С - это одно и то же?

Симптом общий, а сценарий другой: обмен авторизуется своим способом и держит сеанс в cookie. Такой отказ разбирают по странице про ответ точки обмена, а не здесь.

Смежное

Первоисточник