Как правильно сделать сессии и авторизацию на PHP?
Начинающий кодер, прошу сильно не придираться.
PHP 7.0.4 + MySQL 5.7 + PhpStorm 2016.1.2 + XDebug 2.4.0
Захотел написать свою CMS-ку. Сделал каркас, пришло время научить её различать юзеров. Начитался различных тем с подводными камнями и т.д, причем все написаны более 2 лет назад.
На примете 2 варианта:
1. Сессия + БД. Устанавливаем время жизни сессии неделю и не паримся. В таблицу sessions БД пишем session_id, user_id, user_agent, session_expire и сверяем/обновляем их при каждом запросе, кроме AJAX-подобных, хотя и для них можно.
2. Куки + БД. При успешной авторизации создаем в таблице sessions БД запись user_id, random_hash, user_agent, expire. В куки пишем user_id, random_hash. Время действия куков - неделя, при посещении сверяем и продлеваем куки еще на неделю. По сути то же самое, просто меняем $_SESSION на $_COOKIE.
"random_hash" - это просто случайный набор букв, заменяющий session_id в первом примере. Этакий временный пароль, имхо безопаснее хеша пароля пользователя.
При смене пароля либо по запросу юзера все его сессии удаляются из БД и все куки/сессии станут недействительными.
Пока что смотрю в сторону первого варианта.
И еще, как поступать с неавторизованными юзерами? Так же создавать для них сессии? Всё же права доступа они тоже имеют и их можно сравнить с зарегистрированными.
Ульрих: Пожалуй, лучший ответ =) Только вот кода много, надеюсь PhpStorm раскидает по полочкам.
Раньше ведь на Notepad++ пытался кодить, и такие идеи сразу отлетали)
Ульрих: все же совет хороший, но далеко не решение моего вопроса, мне нужны именно примеры реализации, вот kstyle дал ссылки на код, а вы - нет. Напомню, что я начинающий кодер и читать 5 мегабайт кода даже под xdebug-ом для меня не самая простая задачка.
Оба подхода не очень хорошие, так как смешивают логику аутентификации пользователя с логикой протоколирования событий. Условно у вас есть 3 модели: User, Session и UserLog.
Связь UserLog и Session опосредованная через User. Такой подход позволит вам а) организовать хранение сессий в виде "1 пользователь - 1 кука", б) даже если у пользователя умерла кука и ему выдалась новая, вы сохраняете историю пользователя, тк UserLog привязана к User через внешний ключ.
Ах да, забыл сказать что пишу с нуля, без сторонних фреймворков и библиотек, исключением будет разве что jquery и какой-нибудь bootstrap. Но все равно спасибо, может возьму что-нибудь полезное оттуда.
Зачем вообще сессию в БД хранить?
Авторизовался юзер - старт сессии. Браузер закрыл / открыл / время истекло - ещё раз авторизовался.
Всё равно многие пароли в браузерах сохраняют.
Интересуют именно профессиональные решения, а не простые чтобы потестить, поиграться. Вот как мой юзер (или модер) узнает, что на аккаунт кто-то заходил? Как сохранить IP-адреса и useragent-ы без базы данных? Да никак, все современные cms используют это.
Зачем каждый день/час логиниться заново, если можно избавиться от этой рутины?
Александр Казакевич: Вы смешиваете в одну кучу логирование входов и юзер-агентов и сессию, но зачем? Сессия неплохо работает сама по себе, и при этом никто вам не не мешает писать логи хоть в БД хоть в файлы.
Stalker_RED: Ну да, просто я хочу объединить логи, юзер-агенты и сессию в одну запись и называть ее "сессия, контролируемая приложением". Почему нет? Суть не просто в логах. Я планирую любое действие юзера привязывать к его id сессии, а не по дефолту: сохранение IP-адреса и user-agent-а около каждого действия. Модератор сможет просто взять и открыть подробную информацию по сессии, он увидит все действия по порядку: когда, что, откуда. Круто же?
Обычные же сессии удаляются сразу после выхода из аккаунта и такого эффекта уже не выйдет.
Александр Казакевич: Вся информация о действиях которые уже произошли (в прошедшем времени) - по сути логи (история, отчеты, сохранение действий - называйте как хотите). Храняться в базе или в файлах, или их рабы переписывают на глиняные таблички - принципиального отличия нет.
И никто не мешает вам писать в логи идентификатор сессии (sessionid).
И в свете этого непонятно - что такого особенного вы хотите сделать в свем велосипеде, чего нет в стандартном механизме сессии. Принудительно обрывать сессию пользователя? Легко прикручивается.
Я мог бы понять, если бы вы жаловались на то, что при 100500 посетителей у вас проблемы из-за лока файлов, или, жаловались на балансировщик, как упоминал xfg. Тогда вам может посоветовали почитать про php.net/manual/en/session.customhandler.php, и что сессии можно хранить в редисе/мемкеше/любой БД. Но сейчас это выглядит как "мне лень читать доки по сессиям, я хочу изобрести свои". И это настораживает.
safenoob: чтобы иметь персистентное хранение с минимальным оверхедом на чтение-запись. sql все-таки не лучший вариант хранения сессий при заметной нагрузке.
safenoob: $_SESSION также хранит данные персистентно - в /tmp (us.php.net/manual/en/session.configuration.php#ini... Но такая схема не подходит, если проект живет в нескольких дата-центрах. Сессия, созданная в одном дата-центре в другом не валидируется. Разве что как-то расшаривать /tmp, но это извращение.
сделайте абстракцию над сессиями который будет использовать драйвер. Драйвера будут реализовать к примеру хранение сессий в редисе и в файловой системе. И потом пользователь сам настроит что хочет.
Предлагаете использовать redis для хранения сессий? А что насчет поддержки хостингами, везде ли это доступно и плюсы/минусы по сравнению с сессиями в php?
Александр Казакевич: я же сказал сделать драйвера. Драйвер для session storage в редисе, мемкеше, облаке и стандартное средство php. А потом пользователь в конфиге сам выберет
Александр Казакевич: вам о полиморфизме говорят. Классу сессий не стоит знать, где в действительности будут храниться сессии. Для этого нужно описать интерфейс хранения сессии. Класс хранения (драйвер) должен реализовывать этот интерфейс. В конструкторе класса сессий принимаем параметр типизированный нашим интерфейсом.
Итого получается, что вы не зависите от хранилища. Можете передать в конструктор класса сессий любой драйвер, который реализует интерфейс и всё будет работать, без изменения кода.
Теперь о плюсах. Сессии в php хранятся в файлах. Это означает, что когда вы поставите лоад балансер и позади него 2+ бекенд сервера, у вас запросы от клиента начнут приходить то на один сервер, то на другой. Пользователь будет от запроса к запросу, то залогинен, то разлогинен. Конечно есть решения, липкие сессии, php вроде настраивается на хранение в redis. Но самое лучшее решение, это абстрагироваться от механизма хранения сессий в классе сессий и предложить несколько вариантов различного хранения и пользователь уже сам через конфиг сможет в нужный момент времени поменять механизм хранения с одного на другой и даже написать свой, если стандартные не устроят.
Но новичку это будет сложно понять. Сначала нужно, какую-нибудь простую книгу по паттернам прочитать, где расскажут, что такое композиция, зачем нужна и чем она лучше наследования.
Вообще PHP как первый язык, не очень хорошо, дает много свободы и поэтому на нем так много раздолбайского кода. Если бы начинать со строго типизированного языка, как Java, то эффективность обучения была бы в разы выше, но и порог вхождения бы тоже вырос, но не в разы. Но это в идеале, конечно. Сам я тоже с PHP начинал.
В БД для таблицы юзеров добавляешь, например auth_token, при входе его генерируешь и пишешь в куку. При заходе пользователя с кукой, в которой есть нужный токен - логинишь под нужным юзверем.
Кирилл Несмеянов: Ну вот вы напиали: при авторизации для юзера генерируется новый токен. Сначала авторизуюсь с компа, потом с телефона. То есть токен меняется, и при обновлении страницы на компе сессия закрывается. Все верно?
Александр Казакевич: если прямо по пунктам - да, но никто не мешает не перетирать токен, а писать новый только если его сейчас нет или пользователь нажал кнопочку "разлогиниться со всех устройств".
Ситуаций может быть масса и все их предусмотреть маловероятно. Я описал самый простой вариант, а как его уже улучшать\переделывать - всё зависит от требований.
Александр Казакевич: конкретно в данном случае будет пичалька. и я даже знаю ресурсы где так и сделано.
но обычно это решают при помощи еще одной таблицы вида
user_id | token | ....
ибо сейчас у многих юзеров (если не у большинства) больше одного устройства.
DevMan: а если куки копирнуть на другое устройство (например, злоумышленник), то как это распознать на стороне сервера? Токен это же по сути обычная строка.
Кирилл Арутюнов: > если куки копирнуть на другое устройство
есть ряд параметров, по которым токен можно привязать к конкретному устройству.
по простому: юзер-агент/ip/таймзона/etc.
есть более продвинутые средства вида device fingerprint.
Можно добавить прослойку между пользователем и его авторизацией - "посетитель". Это тот, кому выдается токен и кто сидит в одном браузере с одного устройства. Один пользователь может сидеть на сайте как несколько посетителей, у каждого из которых, при желании, отслеживается история. Авторизация на разных устройствах при этом сохраняется, токены "протухают" только по времени последнего визита.
Виталий Инчин ☢: обычно гадят сообщениями в чатике. По вреду, это ведь одно и то же (практически отсутствует), но обязательно сделаю, если на тестах скрипт будет проседать.