
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[216.73.216.3] |
![]() |
|
Сообщ.
#1
,
|
|
|
Здравствуйте.
Хочу разобраться как устроена регистрация в веб-приложениях. Составил простой сценарий как я его понимаю. Если есть замечания, прошу написать или переписать сценарий как считаете верным. Шаги регистрации и API методы: 1. Заполняется форма на странице емейл, пароль и отправляется на сервер. Происходит обращение у API методу 1, который в параметрах принимает: емейл, пароль. 2. Проверки - валидный емейл и пароль - емейла еще нет в системе Не успех: ответ об ошибке. Успех: сообщение о письме-активации отправленном на емейл. 3. При успехе а. В БД в таблицу users добалуется пользователь. Заполнится столбцы - uuid пользователя - имя - емейл - JWT-токен (зашифрованы емейл и имя пользователя) - соль для пароля - хеш пароля - статус (не активирован / активирован) б. Отправляется письмо на указанный емейл. В письме ссылка JWT-токен 4. Пользователь переходит по ссылке из письма. Отправляется запроса к API методу 2, который в параметре принимает JWT-токен. Происходит активация пользователя. Отправляет письмо об успешно активации Не успех: JWT-токен не верный / уже активирован. Успех: статус пользователя в БД => активирован. Дополнительно: При переходе по ссылке в письма - бэкенд ставит куку с JWT-токеном в браузер пользователя - бэкенд проверяет куку в браузер пользователя - если JWT-токен актуален – пользователя пускает в кабинет. |
Сообщ.
#2
,
|
|
|
Цитата rownong@yandex.ru @ - JWT-токен (зашифрованы емейл и имя пользователя) Это лучше хранить отдельно. Они генерируются для каждого входа с разных устройств для пользователя и их может быть больше одного для каждого пользователя. По сути jwt стоит генерировать для такой сущности как сессия, а не пользователь. Цитата rownong@yandex.ru @ - соль для пароля Это не нужно хранить в БД. Оно нужно только для создания защищенного хэша пароля. Если его сохранить, тогда (наверное) станет возможно прочитать пароль. Цитата rownong@yandex.ru @ б. Отправляется письмо на указанный емейл. В письме ссылка JWT-токен Их лучше не использовать в URL ссылках. Они не очень защищенные. ![]() ![]() const jwtok="YOUR JWToken"; const parts = jwtok.split('.'); console.log(parts.map((part, index) => index < 2 ? atob(part) : part)); Попробуйте код выше для вашего токена. Цитата rownong@yandex.ru @ 4. Пользователь переходит по ссылке из письма. Отправляется запроса к API методу 2, который в параметре принимает JWT-токен. Происходит активация пользователя. Отправляет письмо об успешно активации Детализируем (прямые ссылки на backend лучше не генерировать): Ссылка ведет на фронтенд, который загружается и показывает сообщение о успешном подтверждении. Одновременно он активирует backend метод, который может отправить письмо об успешной активации пользователю на почту. |
Сообщ.
#3
,
|
|
|
Цитата macomics @ Это не нужно хранить в БД. Оно нужно только для создания защищенного хэша пароля. Если его сохранить, тогда (наверное) станет возможно прочитать пароль. А где соль (какое-то слово) физически хранится? В файле с кодом "file.php" который отвечает за добавление соли? Добавлено Цитата macomics @ Их лучше не использовать в URL ссылках. Они не очень защищенные. А что тогда в ссылку для подтверждения помещается? |
Сообщ.
#4
,
|
|
|
Цитата rownong@yandex.ru @ А где соль (какое-то слово) физически хранится? В файле с кодом "file.php" который отвечает за добавление соли? Нет. Она создается во время хеширования пароля и более ни где не хранится. Если рассмотреть применение соли в PostgreSQL тогда вы увидите следующее https://postgrespro.ru/docs/postgresql/17/p...ING-FUNCS-CRYPT Вот как раз в том примере entered password это уже повторно введенный пользователем пароль для логина, а вот pswhash - это сохраненный в базе хэш пароля. Как видите после update соль не нужна для проверки совпадения паролей. Цитата rownong@yandex.ru @ А что тогда в ссылку для подтверждения помещается? Можно и токен, только не весь. Только 3-ю его часть |
Сообщ.
#5
,
|
|
|
Цитата macomics @ Можно и токен, только не весь. Только 3-ю его часть Несколько запутался. Что такое "3-часть токена"? Добавлено Цитата macomics @ Нет. Она создается во время хеширования пароля и более ни где не хранится. Я правильно понял, что: 1. Соль - это случайная строка которая возникает в момент генерации хеша? 2. Строка для каждого пароля всегда случайная. 3. Генерация соли (случайной строки) на стороне компонента создания хеша и как таковая соль специально отдельной строкой нигде не фиксируется? 4. Т.е. соль для каждого пароля - никому не известна (не создателю системы и не кому другому)? |
Сообщ.
#6
,
|
|
|
Цитата rownong@yandex.ru @ Несколько запутался. Что такое "3-часть токена"? Посмотрите что вам напечатает мой код из #2 (Код на JavaScript) Цитата rownong@yandex.ru @ 1. Соль - это случайная строка которая возникает в момент генерации хеша? 2. Строка для каждого пароля всегда случайная. 3. Генерация соли (случайной строки) на стороне компонента создания хеша и как таковая соль специально отдельной строкой нигде не фиксируется? 4. Т.е. соль для каждого пароля - никому не известна (не создателю системы и не кому другому)? 1. Да. Возможно что не строка, а число (зависит от алгоритма). Но главное что случайное. 2. Да. 3. Фиксируется в хеше пароля. т.е. дополнительно её сохранять нет смысла (даже вредно). 4. Не известна (не должна быть известна). Добавлено ![]() ![]() $ cat index.js const jwt = require("jsonwebtoken"); const tok = jwt.sign({ test: "string" }, "abracadabra", { expiresIn: '1h' }); const parts = tok.split('.'); console.log(tok, '\n', parts.map((part, index) => index < 2 ? atob(part) : part)); $ node index.js eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0Ijoic3RyaW5nIiwiaWF0IjoxNzU1NDU4OTU1LCJleHAiOjE3NTU0NjI1NTV9.2LI59SiCcVI_wsgyyJgQLk7GgU-kL2LANFDixXF-OHs [ '{"alg":"HS256","typ":"JWT"}', '{"test":"string","iat":1755458955,"exp":1755462555}', '2LI59SiCcVI_wsgyyJgQLk7GgU-kL2LANFDixXF-OHs' ] Вот я создал токен. Вывел его в консоль и прогнал тот мой код. Его результатом стал вот такой массив. Как можете видеть - jsonwebtoken состоит из 3-х часте. В первой части js объект в формате json кодированный в base64. Он описывает алгоритм шифрования и тип. Во второй части тот объект, что я передал для генерации токена, но дополненный полями с временной меткой в секундах и временной меткой с временем истечения действия токена. Ну а третью чать я не декодировал через base64 т.к. только она и является зашифрованный. Части соединяются в одну строку через разделитель (точку). |
Сообщ.
#7
,
|
|
|
Цитата macomics @ Вот я создал токен. Вывел его в консоль и прогнал тот мой код. Его результатом стал вот такой массив. Как можете видеть - jsonwebtoken состоит из 3-х часте. В первой части js объект в формате json кодированный в base64. Он описывает алгоритм шифрования и тип. Во второй части тот объект, что я передал для генерации токена, но дополненный полями с временной меткой в секундах и временной меткой с временем истечения действия токена. Ну а третью чать я не декодировал через base64 т.к. только она и является зашифрованный. Части соединяются в одну строку через разделитель (точку). Да, теперь я увидел 3 части токена header.payload.signature Спасибо! Погуглил: 1. Header (заголовок) — метаданные токена: алгоритм подписи и тип. 2. Payload (полезная нагрузка) — утверждения (claims): данные о пользователе и сроке действия. 3. Signature (подпись) — криптографическая подпись, гарантирует целостность и подлинность header и payload. Уточните: 1. Зачем нужен Header (заголовок). Кем и как он используется? 2. Для чего в токен вкладывают Payload (полезная нагрузка). Где и как это используется? Можете написать примеры сценариев? 3. Signature (подпись). Эта часть как раз делает токен уникальным ключом? Но не пойму зачем гарантировать подлинность header и payload. И еще подскажите: а. Токены хранятся в таблице БД? б. В одной таблице хранятся и рефреш и акцесс токены? в. Токены хранятся в Редис или обычной Постгресс? |
Сообщ.
#8
,
|
|
|
Цитата rownong@yandex.ru @ 1. Зачем нужен Header (заголовок). Кем и как он используется? Он используется алгоритмом обратной верификации. Когда нужно извлечь данные из токена и проверить их подлинность. Тогда не придется прибегать к лишним запросам БД и проверять наличие данных в ней. Хотя это и не исключается. ![]() ![]() const jwt = require("jsonwebtoken"); const tok = jwt.sign({ test: "string" }, "abracadabra", { expiresIn: '1h' }); const parts = tok.split('.'); console.log(tok, '\n', parts.map((part, index) => index < 2 ? atob(part) : part)); // Проверка и извлечение данных из токена. // В отличии от 3-ей строки этого примера последующая проверяет // 2-ю часть на целостность используя данные из 1-ой части и сравнивая результат с 3-ей частью. // Так же происходит проверка на истечение срока действия токена, если присутствует значение exp в payload. // Функция <(e, d) => e ? null : d> в 3-ем параметре следующей строки позволяет избавиться от блока <try {} catch() {}> и обработать ошибки верификации. const payload = jwt.verify(tok, "abracadabra", (e, d) => e ? null : d); console.log(payload); ![]() ![]() $ node index.js eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0Ijoic3RyaW5nIiwiaWF0IjoxNzU1NDU4OTU1LCJleHAiOjE3NTU0NjI1NTV9.2LI59SiCcVI_wsgyyJgQLk7GgU-kL2LANFDixXF-OHs [ '{"alg":"HS256","typ":"JWT"}', '{"test":"string","iat":1755458955,"exp":1755462555}', '2LI59SiCcVI_wsgyyJgQLk7GgU-kL2LANFDixXF-OHs' ] { test: "string", iat: 1755458955, exp: 1755462555 } Скрытый текст Не пинайте, за то, что я это просто взял из примера выше, а не прогнал в программе т.к. таймстемпы сдают меня с потрохами. Цитата rownong@yandex.ru @ 2. Для чего в токен вкладывают Payload (полезная нагрузка). Где и как это используется? Можете написать примеры сценариев? Это могут быть любые ваши данные, которые пользователь предоставил вам, но они должны храниться у него и без изменения. Вываливая эти данные на email и запихивая их в ссылку вы передаете персональную информацию пользователя третьей стороне. Вот сценарий: Пользователь входит в вашу систему (проходит верификацию, предоставляя логин и пароль). Вы получаете его верификационные данные и находите заданную пару в таблице пользователей. После этого у вас есть не только логин и пароль, но и другие связанные с пользователем личные данные, указанные при регистрации. Не найдя все эти данные пользователю сообщается, что он не авторизован и надо бы зарегистрироваться. При успешном поиске в таблице пользователей обычно хранится: Имя, фамилия, дата рождения, город, ключи от квартиры где деньги лежат ![]() После успешной авторизации таким способом вам надо для пользователя открыть новую сессию (создать новый токен и записать его в таблицу токенов вместе с uid пользователя). Таким образом вы зафиксирует факт входа в систему этим пользователем с некоторого устройства. ip и user-agent из заголовков тоже могут быть записаны в эту таблицу, чтобы этим токеном нельзя было пользоваться с другого устройства этого же пользователя. Далее созданные токен устанавливается в заголовке Set-Cookie ответа на запрос входа и далее он будет храниться как у вас в БД (но вам уже достаточно хранить только 3-ю часть) так же и на компьютере пользователя. У вас же будет зафиксировано это устройство через его ip и User-Agent. Тогда, закрыв и снова открыв страницу, вы получите в cookie ваш токен и сможете возобновить сессию. Но вот я все же предпочитаю этот токен из cookie копировать в заголовок Authorization в первом запросе к backend с возобновлением старой сессии чтобы гарантировать, что фронтенд переключился и работает в режиме авторизованного пользователя. Еще может возникнуть две ситуации. Первая: страница пользователя все еще открыта и работает, но токен истек. Тогда нужен механизм повторной авторизации на сервере без запроса данных у пользователя. Вторая: страница снова открыта, а токен из cookie уже истек. Второе обычно решается путем установки срока действия cookie равного (или на секунду меньшего) сроку действия токена. Тогда пользователя можно и повторно спросить о регистрационных данных, если не предусмотреть механизма продления действия такой сессии. Хотя лучше так не делать. Цитата rownong@yandex.ru @ 3. Signature (подпись). Эта часть как раз делает токен уникальным ключом? Но не пойму зачем гарантировать подлинность header и payload. Так организуется механизм распределенного хранения данных на пользовательских компьютерах. Подлинность прежде всего говорит вам, что этому токену можно верить. Но! Раскодировав header и payload из токена редиски могут попытаться подобрать пароль ("abracadabra") и создать свой токен. Поэтому часть данных (3-ю) из него нужно хранить у себя, чтобы точно знать что этот токен создан вами. Т.к. в токен входит поле iat то третья часть будет всегда разной даже для двух токенов с одним payload созданных в разное время. Добавлено Цитата rownong@yandex.ru @ а. Токены хранятся в таблице БД? Можно хранить как полностью так и частично (только контрольную сумму из 3-ей части). Надо понимать, что сохраняя первые две части токена в БД вы будете забивать её избыточной (уже присутствующей в БД - payload) или константной информацией (header). Цитата rownong@yandex.ru @ б. В одной таблице хранятся и рефреш и акцесс токены? Это два разных механизма, но сами токены идентичны. Если снабдите таблицу полем для различия этих типов токенов, тогда можно хранить и в одной таблице. Но лучше в разных. Цитата rownong@yandex.ru @ в. Токены хранятся в Редис или обычной Постгресс? Храните их хоть в текстовом файле. Важно не где а для чего. Вам надо хранить их у себя, хотя бы частично, чтобы иметь возможность отличить собственный токен от сгенерированного третьими лицами. Да хоть предыдущими, не совместимыми с текущей, версиями этого же проекта. |