На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Serafim, fatalist
  
    > Базовые вопросы по процедуре регистрации в веб-приложениях
      Здравствуйте.
      Хочу разобраться как устроена регистрация в веб-приложениях.
      Составил простой сценарий как я его понимаю.
      Если есть замечания, прошу написать или переписать сценарий как считаете верным.

      Шаги регистрации и API методы:
      1. Заполняется форма на странице емейл, пароль и отправляется на сервер.
      Происходит обращение у API методу 1, который в параметрах принимает: емейл, пароль.

      2. Проверки
      - валидный емейл и пароль
      - емейла еще нет в системе
      Не успех: ответ об ошибке.
      Успех: сообщение о письме-активации отправленном на емейл.

      3. При успехе
      а. В БД в таблицу users добалуется пользователь. Заполнится столбцы
      - uuid пользователя
      - имя
      - емейл
      - JWT-токен (зашифрованы емейл и имя пользователя)
      - соль для пароля
      - хеш пароля
      - статус (не активирован / активирован)
      б. Отправляется письмо на указанный емейл. В письме ссылка JWT-токен

      4. Пользователь переходит по ссылке из письма.
      Отправляется запроса к API методу 2, который в параметре принимает JWT-токен.
      Происходит активация пользователя.
      Отправляет письмо об успешно активации

      Не успех: JWT-токен не верный / уже активирован.
      Успех: статус пользователя в БД => активирован.
      Дополнительно:
      При переходе по ссылке в письма
      - бэкенд ставит куку с JWT-токеном в браузер пользователя
      - бэкенд проверяет куку в браузер пользователя
      - если JWT-токен актуален – пользователя пускает в кабинет.
        Цитата rownong@yandex.ru @
        - JWT-токен (зашифрованы емейл и имя пользователя)

        Это лучше хранить отдельно. Они генерируются для каждого входа с разных устройств для пользователя и их может быть больше одного для каждого пользователя. По сути jwt стоит генерировать для такой сущности как сессия, а не пользователь.

        Цитата rownong@yandex.ru @
        - соль для пароля

        Это не нужно хранить в БД. Оно нужно только для создания защищенного хэша пароля. Если его сохранить, тогда (наверное) станет возможно прочитать пароль.

        Цитата rownong@yandex.ru @
        б. Отправляется письмо на указанный емейл. В письме ссылка JWT-токен

        Их лучше не использовать в URL ссылках. Они не очень защищенные.

        ExpandedWrap disabled
          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 метод, который может отправить письмо об успешной активации пользователю на почту.
        Сообщение отредактировано: macomics -
          Цитата macomics @
          Это не нужно хранить в БД. Оно нужно только для создания защищенного хэша пароля. Если его сохранить, тогда (наверное) станет возможно прочитать пароль.

          А где соль (какое-то слово) физически хранится? В файле с кодом "file.php" который отвечает за добавление соли?

          Добавлено
          Цитата macomics @
          Их лучше не использовать в URL ссылках. Они не очень защищенные.

          А что тогда в ссылку для подтверждения помещается?
            Цитата rownong@yandex.ru @
            А где соль (какое-то слово) физически хранится? В файле с кодом "file.php" который отвечает за добавление соли?

            Нет. Она создается во время хеширования пароля и более ни где не хранится.

            Если рассмотреть применение соли в PostgreSQL тогда вы увидите следующее
            https://postgrespro.ru/docs/postgresql/17/p...ING-FUNCS-CRYPT

            Вот как раз в том примере entered password это уже повторно введенный пользователем пароль для логина, а вот pswhash - это сохраненный в базе хэш пароля. Как видите после update соль не нужна для проверки совпадения паролей.

            Цитата rownong@yandex.ru @
            А что тогда в ссылку для подтверждения помещается?

            Можно и токен, только не весь. Только 3-ю его часть
              Цитата macomics @
              Можно и токен, только не весь. Только 3-ю его часть

              Несколько запутался. Что такое "3-часть токена"?

              Добавлено
              Цитата macomics @
              Нет. Она создается во время хеширования пароля и более ни где не хранится.

              Я правильно понял, что:
              1. Соль - это случайная строка которая возникает в момент генерации хеша?
              2. Строка для каждого пароля всегда случайная.
              3. Генерация соли (случайной строки) на стороне компонента создания хеша и как таковая соль специально отдельной строкой нигде не фиксируется?
              4. Т.е. соль для каждого пароля - никому не известна (не создателю системы и не кому другому)?
                Цитата rownong@yandex.ru @
                Несколько запутался. Что такое "3-часть токена"?

                Посмотрите что вам напечатает мой код из #2 (Код на JavaScript)

                Цитата rownong@yandex.ru @
                1. Соль - это случайная строка которая возникает в момент генерации хеша?
                2. Строка для каждого пароля всегда случайная.
                3. Генерация соли (случайной строки) на стороне компонента создания хеша и как таковая соль специально отдельной строкой нигде не фиксируется?
                4. Т.е. соль для каждого пароля - никому не известна (не создателю системы и не кому другому)?

                1. Да. Возможно что не строка, а число (зависит от алгоритма). Но главное что случайное.
                2. Да.
                3. Фиксируется в хеше пароля. т.е. дополнительно её сохранять нет смысла (даже вредно).
                4. Не известна (не должна быть известна).

                Добавлено
                ExpandedWrap disabled
                  $ 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 т.к. только она и является зашифрованный. Части соединяются в одну строку через разделитель (точку).
                Сообщение отредактировано: macomics -
                  Цитата 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.

                  И еще подскажите:
                  а. Токены хранятся в таблице БД?
                  б. В одной таблице хранятся и рефреш и акцесс токены?
                  в. Токены хранятся в Редис или обычной Постгресс?
                    Цитата rownong@yandex.ru @
                    1. Зачем нужен Header (заголовок). Кем и как он используется?

                    Он используется алгоритмом обратной верификации. Когда нужно извлечь данные из токена и проверить их подлинность. Тогда не придется прибегать к лишним запросам БД и проверять наличие данных в ней. Хотя это и не исключается.

                    ExpandedWrap disabled
                      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);


                    ExpandedWrap disabled
                      $ 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
                      }
                    Теперь в вывод добавился payload из sign, но с двумя дополнительными полями, которые отвечают за срок действия токена.
                    Скрытый текст
                    Не пинайте, за то, что я это просто взял из примера выше, а не прогнал в программе т.к. таймстемпы сдают меня с потрохами.


                    Цитата rownong@yandex.ru @
                    2. Для чего в токен вкладывают Payload (полезная нагрузка). Где и как это используется? Можете написать примеры сценариев?

                    Это могут быть любые ваши данные, которые пользователь предоставил вам, но они должны храниться у него и без изменения.
                    Вываливая эти данные на email и запихивая их в ссылку вы передаете персональную информацию пользователя третьей стороне.

                    Вот сценарий: Пользователь входит в вашу систему (проходит верификацию, предоставляя логин и пароль). Вы получаете его верификационные данные и находите заданную пару в таблице пользователей. После этого у вас есть не только логин и пароль, но и другие связанные с пользователем личные данные, указанные при регистрации. Не найдя все эти данные пользователю сообщается, что он не авторизован и надо бы зарегистрироваться.
                    При успешном поиске в таблице пользователей обычно хранится: Имя, фамилия, дата рождения, город, ключи от квартиры где деньги лежат ;), но главное есть индекс uid, который используется для связи данных с пользователем в других таблицах БД. Вот этот uid можно воткнуть в payload к токену без опасения. А он будет указывать вам на строку в таблице с логином, который будет представляться из себя email. Можно воткнуть еще и имя с фамилией и датой рождения, но тогда токен нельзя будет передавать в ссылках уже точно не нарушая при этом закон о персональных данных. По сути вы тем самым в открытую передаете эти данные 3-им лицам.
                    После успешной авторизации таким способом вам надо для пользователя открыть новую сессию (создать новый токен и записать его в таблицу токенов вместе с 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 @
                    в. Токены хранятся в Редис или обычной Постгресс?

                    Храните их хоть в текстовом файле. Важно не где а для чего. Вам надо хранить их у себя, хотя бы частично, чтобы иметь возможность отличить собственный токен от сгенерированного третьими лицами. Да хоть предыдущими, не совместимыми с текущей, версиями этого же проекта.
                    Сообщение отредактировано: macomics -
                    1 пользователей читают эту тему (1 гостей и 0 скрытых пользователей)
                    0 пользователей:


                    Рейтинг@Mail.ru
                    [ Script execution time: 0,0387 ]   [ 14 queries used ]   [ Generated: 13.09.25, 00:04 GMT ]