Как защитить PHPSESSID от кражи и оставить доступ к токену для «своего» JS? Верно ли я мыслю?
С чем имеем дело:
У меня есть сервер для чата, написанный на Ratchet. Проблема в том, что для доступа к сессии пользователя я не могу просто посмотреть в $_SESSION и узнать то, что мне нужно, например, авторизован ли юзер вообще.
Поэтому, чтобы это исправить я просто беру из document.cookie PHPSESSID, отправляю его на сервер, там нахожу файлик, который относится к этой сессии и у меня есть вся информация о сессии пользователя.
Суть проблемы
Одним из способов защиты cookie PHPSESSID является сохранение куки как httponly. Таким образом хакер не сможет через вредоносный JS получить этот куки и отправить его к себе на сервер.
Но в таком случае и я не смогу получить этот куки, чтобы его отправлять на свой WS-сервер.
По HTTP(S) можно запросить временный токен (сервер по кукам понимает, какому пользователю его выдал), по сокету отправить сообщение типа "я такой-то, вот подтверждающий токен, аутентифицируй меня", сервер проверяет, и после успешного завершения проверки начинает считать данный канал связанным с указанным пользователем.
Можно также спокойно использовать HTTP(S) для всего, что не относится непосредственно к реалтайм-приёмопередаче (чат-сообщениям).
Можно также спокойно использовать HTTP(S) для всего, что не относится непосредственно к реалтайм-приёмопередаче (чат-сообщениям).
Допустим Вася хочет написать Пете. Вася отправляет сообщение AJAX'ом на сервер, а сервер отсылает это же сообщение при помощи WebScocket Пете?
Вы имеете в виду эту схему? Если да, то это тоже неплохой вариант, но все-таки лучше изначально JavaScript'ом слать сообщение по webscocket на сервер, я думаю.
LeonidPokrovskiy, нет, я имею в виду, что аутентификационные вещи проводятся в первую очередь через https. Куки хранятся там же. Чтобы подтвердить личность пользователя, по сокетам чат-серверу (Vasya→ServiceBot) отправляется что-то типа "/auth Vasya ad851d41b324b22303223cb397297fc1" (насколько я понимаю, на верхнем уровне Ratchet оперирует сообщениями), где "/auth" — условная команда, "Vasya" — имя пользователя (или любой идентификатор), абракадабра — токен, полученный от сервера по https (а-ля https://chat-server.example.com/getTmpAuthToken/ , сервер по кукам понимает, кому даёт токен, и записывает у себя в БД, что с этой абракадаброй может аутентифицироваться по сокетам юзер Vasya в течение 10 минут).
LeonidPokrovskiy, После проверки сообщения сокет-соединение маркируется на стороне сервера как принадлежащее пользователю Vasya. Тут возникает вопрос, можно ли в Ratchet проставить соединению такую маркировку, т. к. с данным инструментом я не знаком.
Александр Шохров, то есть алгоритм такой (на примере Васи, который Пете отправляет сообщение):
1. JavaScript запрашивает у сервера по HTTP(S) временный токен, который сервер у себя "свяжет" с оригинальным PHPSESSID. В этот момент PHP в базу данных (пусть будет условная называться secret_temp_tokens) записывает, что Вася по такому-то токену может что-то сделать.
2. JavaScript отправляет по WebSocket сообщение пользователя и этот самый токен, который JavaScript попросил у сервера.
3. Тут в игру вступает WebSocket сервер. Он смотрит в базу данных secret_temp_tokens и если токен, который дал JavaScript есть в таблице, значит помечаем в таблице secret_temp_tokens как использованный и отправляем Пете сообщение. Таким образом даже если хакер сворует этот токен, то толку от него никакого не будет.
Кстати, даже если хакер и умудрится украсть токен и воспользоваться им до того, как Вася отправил сообщение, то в таком случае и это можно предотвратить. Можно к каждому секретному токену привязывать IP-адрес, чтобы хакер не смог воспользоватья токеном. В таком случае даже пользователи с динамическим IP не будут иметь никаких проблем. Как вам такая идея?
LeonidPokrovskiy, примерно так. Я не профессиональный советчик в теме, но практика весьма распространённая. Обычно ещё делают ограничение на время использования токена, сохраняя в БД время его генерации или сразу время истечения. Если его пытаются использовать после времени истечения (время генерации + пару минут, например) — фейл.
Да, можно также записывать дополнительную информацию а-ля айпишник, теоретически иногда такая подробность проверки может мешать аутентифицировать валидного пользователя, но тут уже смотреть надо, кто и где будет использовать.
В идеале, выполнять проверку в момент соединения пользователя с сокет-сервером, и в зависимости от результата принимать или обрывать соединение.
LeonidPokrovskiy, также замечу: причина, по которой я переформулировал часть комментария, упоминающую ссылку — насколько я понял, там приводится не готовое решение, а концепции (насколько вижу в исходниках, $conn (ConnectionInterface) не содержит, собственно, httpRequest, где можно посмотреть нужное. То есть нужно найти место в архитектуре Ratchet'а, в которое эту процедуру можно внедрить.
Александр Шохров, я давно еще видел httpRequest, когда пытался доступ к сессиям с WS-сервера получить. Нет там ничего. Когда делается запрос от JS к WS-серверу, ничего не приходит, кроме того, что я и указал при отправке. То есть никаких заголовков, никаких куков - только тело, если можно так выразиться.
То есть нужно найти место в архитектуре Ratchet'а, в которое эту процедуру можно внедрить.
Не получится, я думаю. Там в принципе нет никакой информации и куках и т.п. Это другой протокол вообще. Это не просто AJAX, где все вместе сразу отправляется.
В общем, самый разумный вариант - это временные токены, насколько я понял.
LeonidPokrovskiy, всё это время я имею в виду вариант с токеном, а в цитируемом комментарии — доступ к исходному запросу в сессии WebSocket'ов, т. к. установка соединения там начинается практически по HTTP. В этом исходном запросе и можно передать токен, либо как параметр в URL, либо в заголовках. Вопрос в том, чтобы перехватить эту информацию на сервере.
Вот HttpServer в Ratchet'е ещё видит запрос, судя по сигнатуре onOpen. Как вариант — дополнить этот класс, или сделать обёртку для WsServer.
Решение с SessionProvider, как понимаю, использует куки из основной, несокетной сессии, т. е. их надо брать и перекидывать JS-ом, поэтому не подходит.
LeonidPokrovskiy, а вы сами передавали что-нибудь со стороны клиента? Токен параметром в ссылке на подключение, например. Для шаринга куков как минимум домен должен быть одинаковый, мб ещё что-то.