Laravel возвращает "несоответствие токена CSRF", хотя токен CSRF включен и по-прежнему соответствует

Я пишу Android-клиент, основанный на Retrofit и OkHttp, для веб-службы, основанной на Laravel и использующей защиту CSRF. Хотя я отправляю правильный токен CSRF (по крайней мере, я так считаю), веб-служба отвечает кодом состояния HTTP «419 - несоответствие CSRF». (Примечание: 419 не является официальным кодом статуса HTTP, но специфичен для Laraval).

Где ошибка в моем втором HTTP-запросе? (См. ниже).

Точнее пытаюсь написать клиент для Личи. Веб-API Lychees задокументирован здесь. На первом этапе я отправляю HTTP-запрос к php/index.php, чтобы получить токен CSRF. Этот первый шаг увенчался успехом. На втором этапе я отправляю HTTP-запрос к /api/Session::login. Хотя заголовок запроса включает токен CSRF, этот запрос не выполняется. Почему?

1-й запрос:

LycheeClient: --> POST https://devel.my-domain.tld/php/index.php
LycheeClient: Content-Length: 0
LycheeClient: Accept: application/json, text/javascript, */*; q=0.01
LycheeClient: DNT: 1
LycheeClient: Origin: https://devel.my-domain.tld
LycheeClient: --> END POST (0-byte body)

1-й ответ:

LycheeClient: <-- 200 https://devel.my-domain.tld/php/index.php (959ms)
LycheeClient: date: Mon, 05 Apr 2021 19:03:22 GMT
LycheeClient: server: Apache
LycheeClient: cache-control: no-cache, private
LycheeClient: phpdebugbar-id: X1217d2d3c26dc642b6683c76249c3703
LycheeClient: feature-policy: autoplay 'self'; encrypted-media 'self'; fullscreen 'self'; picture-in-picture *; sync-xhr *
LycheeClient: x-content-type-options: nosniff
LycheeClient: x-download-options: noopen
LycheeClient: x-frame-options: deny
LycheeClient: x-permitted-cross-domain-policies: none
LycheeClient: x-xss-protection: 1; mode=block
LycheeClient: referrer-policy: no-referrer
LycheeClient: x-clockwork-id: 1617649402-3275-1189279108
LycheeClient: x-clockwork-version: 4.1.8
LycheeClient: set-cookie: XSRF-TOKEN=eyJpdiI6InRTanVBTmdBenpSMUlFV2hDMkgycnc9PSIsInZhbHVlIjoiOU0yY2pKMG5PVTY0SndoWnIzeUVxd0UzbHkrTHUxYzRjazNia3hEdHVqYUhyN1JMRG5nbkJmUFFXZjFWdFpnTzM2Z014bnpPSEYvTUZKdGkzRXM1SlRxSXdVSngwZWNBRUZJNysrOVlid2xocEg0L3BOZlVlNnltWFhNREpjS1EiLCJtYWMiOiJmZmYwZjUxZDlhNWYxZGIxYWM3NGE3MjUyZjY5Y2I0ODFjYWRlZDliZmUxNTZkNGI4NjAzODI3MmVlOTZkMzE3In0%3D; expires=Mon, 05-Apr-2021 21:03:22 GMT; Max-Age=7200; path=/; samesite=lax
LycheeClient: set-cookie: lychee_session=eyJpdiI6ImdwYXdTN2x3L0VMRmgwaXNmMG1DbVE9PSIsInZhbHVlIjoiZmNXbnNPc1FSQk42aVk3OTJUd1dNSUk0NUJqT1kxT2paWktqZWdKQ21DUFNRcWpuQmx1VUFiYUVYWW5RU2lSSEhMMVZzejFDY3E0S3pLQS9IZDJ0ViswRElQUVJOWVpQRVJUNmM3UDhsY0xQUi9yV3FoSjhseVdHS2kyck9oS0EiLCJtYWMiOiIwNzcxOWEyOGZhMGFlZDE5MzhlM2Q3ZDcwODgwOGMwZWJmYzNkMDQxNmMwOGYzYWU4ZDRhNTljNzViYzYzNTk4In0%3D; expires=Mon, 05-Apr-2021 21:03:22 GMT; Max-Age=7200; path=/; httponly; samesite=lax
LycheeClient: x-robots-tag: none
LycheeClient: strict-transport-security: max-age=31104000; includeSubDomains
LycheeClient: content-type: application/json
LycheeClient: <-- END HTTP (20941-byte body)

2-й запрос: (токен включен)

LycheeClient: --> POST https://devel.my-domain.tld/api/Session::login
LycheeClient: Content-Type: application/json; charset=UTF-8
LycheeClient: Content-Length: 37
LycheeClient: Accept: application/json, text/javascript, */*; q=0.01
LycheeClient: DNT: 1
LycheeClient: Origin: https://devel.my-domain.tld
LycheeClient: X-XSRF-TOKEN: eyJpdiI6InRTanVBTmdBenpSMUlFV2hDMkgycnc9PSIsInZhbHVlIjoiOU0yY2pKMG5PVTY0SndoWnIzeUVxd0UzbHkrTHUxYzRjazNia3hEdHVqYUhyN1JMRG5nbkJmUFFXZjFWdFpnTzM2Z014bnpPSEYvTUZKdGkzRXM1SlRxSXdVSngwZWNBRUZJNysrOVlid2xocEg0L3BOZlVlNnltWFhNREpjS1EiLCJtYWMiOiJmZmYwZjUxZDlhNWYxZGIxYWM3NGE3MjUyZjY5Y2I0ODFjYWRlZDliZmUxNTZkNGI4NjAzODI3MmVlOTZkMzE3In0
LycheeClient: Cookie: XSRF-TOKEN=eyJpdiI6InRTanVBTmdBenpSMUlFV2hDMkgycnc9PSIsInZhbHVlIjoiOU0yY2pKMG5PVTY0SndoWnIzeUVxd0UzbHkrTHUxYzRjazNia3hEdHVqYUhyN1JMRG5nbkJmUFFXZjFWdFpnTzM2Z014bnpPSEYvTUZKdGkzRXM1SlRxSXdVSngwZWNBRUZJNysrOVlid2xocEg0L3BOZlVlNnltWFhNREpjS1EiLCJtYWMiOiJmZmYwZjUxZDlhNWYxZGIxYWM3NGE3MjUyZjY5Y2I0ODFjYWRlZDliZmUxNTZkNGI4NjAzODI3MmVlOTZkMzE3In0%3D
LycheeClient: --> END POST (37-byte body)

Второй ответ:

LycheeClient: <-- 419 https://devel.my-domain.tld/api/Session::login (181ms)
LycheeClient: date: Mon, 05 Apr 2021 19:03:23 GMT
LycheeClient: server: Apache
LycheeClient: cache-control: no-cache, private
LycheeClient: phpdebugbar-id: Xfec172b4e199e85ba49731cef5942e7b
LycheeClient: feature-policy: autoplay 'self'; encrypted-media 'self'; fullscreen 'self'; picture-in-picture *; sync-xhr *
LycheeClient: x-content-type-options: nosniff
LycheeClient: x-download-options: noopen
LycheeClient: x-frame-options: deny
LycheeClient: x-permitted-cross-domain-policies: none
LycheeClient: x-xss-protection: 1; mode=block
LycheeClient: referrer-policy: no-referrer
LycheeClient: x-clockwork-id: 1617649403-0046-418871694
LycheeClient: x-clockwork-version: 4.1.8
LycheeClient: server-timing: app; dur=49.594879150391; desc = "Application", db; dur=26.89; desc = "Database", timeline-event-total; dur=49.700975418091; desc = "Total execution time.", timeline-event-initialisation; dur=5.7110786437988; desc = "Application initialisation.", timeline-event-boot; dur=4.6510696411133; desc = "Framework booting.", timeline-event-run; dur=43.990135192871; desc = "Framework running."
LycheeClient: set-cookie: lychee_session=eyJpdiI6ImZuN1JGZmVTQjBzMzc1RWFtdWFDMFE9PSIsInZhbHVlIjoiNE5GdWY3UWFuV3NUZ2VHcEdIUnNnbTNDbXVNa0JZKzJjWGwyd2s4RnArclg3ZUZoU1F3cUwrQ1FLNHlBTzk4SUVpdjZGaVU3Mkt6eEJJOExPeEpRcGJJU3ZpNC9LVy8xQXRQRjd3MVBzQk1CZDM5eXJnY20ySVhXMlRSc24ya1QiLCJtYWMiOiJmZmUyMmMwNWIzYTY0NWMxNTkyNjJlNGM1YzZmOWY2NWM5OTBmOWQxNDAwZTI4YTFlNGIzZTk2MzViNTg3NmE1In0%3D; expires=Mon, 05-Apr-2021 21:03:23 GMT; Max-Age=7200; path=/; httponly; samesite=lax
LycheeClient: x-robots-tag: none
LycheeClient: strict-transport-security: max-age=31104000; includeSubDomains
LycheeClient: content-type: application/json

Второй ответ:

{
  "message": "CSRF token mismatch.",
  "exception": "Symfony\\Component\\HttpKernel\\Exception\\HttpException",
  "file": "/var/www/mhnnet/lychee-dev/vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php",
  "line": 373,
  "trace": [ ... ]
}

Вам также необходимо отправить файл cookie сеанса с токеном. Я не вижу этого во втором запросе

apokryfos 05.04.2021 22:56

Вы абсолютно правы. Я включил cookie сеанса в предыдущий тест, который тоже не увенчался успехом, и поэтому я удалил cookie сеанса, чтобы посмотреть, не изменится ли что-нибудь. Конечно, сеансовые куки необходимы. Извините, что опубликовал неправильный вывод. Настоящая проблема в другом. Маркер CSRF, который отправляется клиенту в заголовке Set-Cookie, заканчивается на %3D. Если значение токена CSRF копируется в заголовок X-XSRF-TOKEN следующего запроса, этот завершающий %3D должен быть удален из значения. Почему?

user2690527 06.04.2021 12:47

Я не совсем уверен в этом, %3D - это = с urlen-кодированием. Я подозреваю, что это потому, что для файла cookie вам нужно urlencode =, но для заголовка вы этого не делаете, поэтому вы можете заменить его на =, однако здесь он, похоже, работает с ним или без него, потому что = обычно указывает заполнение в строке base64 так что может быть не обязательно

apokryfos 06.04.2021 13:19

Нет, это не только не нужно, %3D нельзя помещать в заголовок, и его нельзя заменять на =, он должен быть удален полностью. Если в заголовке присутствует %3D или =, Laraval отвечает кодом 419. Хорошо, похоже, мне нужно удалить заполнение, когда токен CSRF отправляется обратно на сервер. Это задокументированное поведение по умолчанию?

user2690527 06.04.2021 14:34

Нет, на самом деле это не кажется правильным поведением, если честно. XSRF-TOKEN - это зашифрованный токен, который включает в себя проверочную подпись, поэтому его изменение и отправка обратно обычно приводят к сбою проверки подписи. Не уверен, что здесь происходит

apokryfos 06.04.2021 18:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Поиск нового уровня в Laravel с помощью MeiliSearch и Scout
Поиск нового уровня в Laravel с помощью MeiliSearch и Scout
Laravel Scout - это популярный пакет, который предоставляет простой и удобный способ добавить полнотекстовый поиск в ваше приложение Laravel. Он...
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
1
5
33
0

Другие вопросы по теме