Pull to refresh

Comments 84

Спасибо за статью. Немного намешано разных аспектов аутентифкации, но в основном написаны правильные вещи.
И ещё один, менее (пока) популярный (и доступный только на устройствах Apple) метод беспарольной аутентификации: использовать Touch ID для аутентификации по отпечаткам пальцев.

Тут надо отметить, что Touch ID обычно только заменят PIN-код при доступе к криптографическим ключам, которые используются для аутентификации.
Кстати есть решения и с новомодным FaceID.
Что касается биометрии, то ее следует рассматривать как локальную аутентификацию на устройстве. Я бы избегал систем, где биометрические данные используются для централизованной аутентификации.

Одним из трендов в аутентификации обещает стать FIDO U2F. Он, к сожалению, не упоминается в этой статье. Про него очень неплохо написано тут.
Что-то с токенами я не очень понял: если у каждого клиента на сервере хранится свой ключ для подписи токена, то один фиг придется лазить в базу при каждом запросе, если же один ключ для подписей всех токенов, то непонятно как инвалидировать скомпрометированные токены?
Очевидно, что сервер подписывает ответ своим ключом для всех токенов.
Если аутентификация stateless, то инвалидировать нельзя никак. Единственное, чем вы можете управлять это время жизни токена. Нужно с умом выбирать тот или иной способ аутентификации в соответсвии с конкретной задачей. И конечно не стоит слепо гнаться за «набирающими популярность» вещами. Надо все самому взвешивать и оценивать.
Ну почему нельзя, можно.
Если приходит юзер с токеном, то, как правило, там же приходит и user_id, по которому мы будем загружать из БД объект этого юзера. Ничего не мешает хранить в этом объекте и минимальную дату годного токена. Если нужна инвалидация — то просто обновляем эту дату в БД до текущей и все токены с ранними датами протухают.
Вы предлагаете по сути хранить в БД время жизни токена. Так мы пришли к противоречию, так как предложенная вами реализация совсем не stateless.
Для задач, когда хочется и токены использовать и контролировать статус токена, удобнее использовать список отозванных токенов по аналогии CRL для сертификатов.
Токенов может быть много, дата — одна, список токенов хранить не надо.
Еще раз: если для аутентификации вам надо запрашивать статус токена любым способом, то вы теряете stateless. Совсем не важно, одна дата для всех токенов или для каждого индивидуальная. При масштабировании вам придется поддерживать актуальность этой даты на всех серверах.
Пожалуйста, не надо меня убеждать, что это совсем чуть-чуть информации или что это легко и просто сделать. Я не это оспариваю сейчас.

Я не спорю с утверждением, что это неподходящего решения для чистого stateless, я дополняю ваш ответ, говоря про случаи, где уже есть пользователь с состоянием (заблокирован/не заблокирован, платная/бесплатная подписка и т.д.). В этом случае это рабочий вариант.

всё-таки в таком варианте токен кажется легко уязвимым.
куки, кажется привязывали к ip, но это хранится в бд сервера, а от нее мы хотим отказаться.
ведь должно быть что-то подобное… ну, например, сервер видит запрос, на основании сведений об ip, браузере, на основании каких-то параметров fingerprint компьютера генерирует хэш, и возвращает нам токен с хешем внутри. И далее при каждом предоставлении токена сервером генерируется и проверяется совпадение хешей. Таким образом получится, что токеном нельзя воспользоваться на другом устройстве/из другой программы.
Такое уже есть? Или проще и считается достаточным короткое время жизни?
Нет никакой проблемы зашить айпи запроса в токен и сверять их при получении токена на последующих запросах.

fingerprint клиента — это в любом случае клиентские подделываемые данные, доверять им не стоит никогда
by-design инвалидировать не получится, только костылить и хранить список отозванных токенов, что по сути возвращает нас к сессиям
По сути — да, но размер базы будет меньше, а поиск быстрее, ведь список отозванных токенов будет куда короче, чем список вообще всех токенов, плюс по истечении времени действия отозванных токенов их можно безопасно удалять.
А разве чтобы отозвать токен нам не придется его где-то хранить на сервере? Иначе как отозвать токен, который где-то там на клиенте? Или я чего-то не понимаю…
Хранит придется только его идентификатор, что очень дешево
Да, нам придется хранить на сервере список отозванных токенов, но это будет куда дешевле, чем хранить вообще все токены. Это некоторый компромисс между удобством валидации токена и возможностью его отозвать
На самом деле не вижу особенной разницы между хранением всех токенов или только отозванных. И в том и другом случае об этом надо думать.
А вопрос объема дело десятое, т.к. на малом объеме это не существенно, а на большом есть проблемы и посерьезнее.

Отозванные за всё время хранить "дёшево"?

Зачем хранить за все время? или вы токены выдаете на года? Хранить надо до истечения времени жизни токена
Вы меня не поняли. Разве не надо в каком-то виде хранить инфу о выданном токене, чтобы потом его можно было отозвать? Как можно отозвать на сервере то чего на сервер нет?
Как сейчас государство отзывает (объявляет недействительными) паспорта, которые лежат у нас в карманах? Принцип примерно тот же — достаточно внести идентификатор в базу. Хранить идентификатор — дешево. Проверить — быстро.
Когда пользователь выходит из системы, токен на клиентской стороне уничтожается, с сервером взаимодействовать не нужно.

И вот тут есть проблема — приложение должно особым образом обрабатывать выход (закрытие вкладки или окна). Так же стоит обратить внимание что токен не может быть отозван серверном до истечения expire. Ну то есть можно, но вообще нет.

Пожалуй единственный недостаток jwt — отсутствие стандартных механизмов верификации. Но это прямо вытекает из его ориентированности на распределённость.

Не совсем понятно, что вы имеете в виду под «стандартными механизмами верификации». Можете ли Вы раскрыть более детально ваш тезис?
Наверно имелось ввиду то, что верифицировать токен можно имея только приватный ключ сервера.
Это не очень удобно если нужно дать возможность верификации другим сервисам (или даже сторонним). Было бы удобно если бы поддерживался еще и открытый ключ для верификации который можно было бы передавать другим.
Вы не поверите:

{"alg":"RS256","typ":"JWT"}
Я имел ввиду, что технология JWT не предусматривает механизма отзыва токена по инициативе сервера, или проверки его актуальности. Без централтного сервера выдачи токенов, или сессионно подобного хранилища этого не реализовать, кмк.

Возможно ограничение целей/приложений где и как долго токен действует.
Или приложение может требовать токен не старше N секунд и отправлять принудительно за новым

Возможно ограничение целей/приложений где и как долго токен действует.

Если в самом токене — это не возможность отзыва при бане, например.
Или приложение может требовать токен не старше N секунд и отправлять принудительно за новым
механизм получения по хорошему должен требовать действий от пользователя (аутентификация), в общем случае. Получается ограничение жизни «сессии».
Не очень понял насчет токенов. Если токен хранится только на клиенте и в нем содержится роль юзера, то что ему мешает подделать ее и прописать себя админом? Или он зашифрованный хранится?

И еще, не надо сыпать сложной терминологией в простой статье без пояснений, пожалуйста. :( Я ничего не знаю про репликацию. Я даже не знаю что это.

А так вообще очень крутая статья. Хорошо все объяснено.
В статье приведена ссылка на английский ресурс, где очень хорошо рассказывается про JWT.
Если токен хранится только на клиенте и в нем содержится роль юзера, то что ему мешает подделать ее и прописать себя админом?

Этому препятствует тот факт, что токен криптографически подписан.
На расшифровку подписи при каждом запросе будут тратиться ресурсы сервера.
Значит у сессий и у токенов один общий недостаток остается — нагрузка на сервер.
Нагрузка, которой вполне можно пренебречь
Вы вот шутите первой картинкой: «я использую только md5», а я сейчас как раз правлю сайт, в котором все пароли вместе с эмейлами лежат в таблице MySQL без любого шифра…
А вы говорите, «md5»…
UFO landed and left these words here
Правильное решение в PHP — это хэшировать с помощью password_hash.
А без соли хранить md5 нельзя, так как в наши дни это равнозначно отсутствию хеширования — сейчас существует масса таблиц, в которых можно запросто найти все популярные хеши md5.

Хранить md5 нельзя ни с солью, ни без. Это очень быстрый алгоритм. А массу радужных таблиц можно найти далеко не только для md5.

Раз уж вы так уверены в том, что MD5 хранить нельзя, то, наверное, знаете когда для него нашли прообраз, да? Ну или хотя бы для MD2, который, как известно, ещё хуже, чем MD5?

Правильный ответ
Ни MD5, ни MD2 до сих пор не взломаны. Атака дней рождения успешно убивается случайной солью, а без этого — требуется память, измеряемая зеттабайтами даже для MD2
Не совсем понимаю, причём тут атака дней рождения и случайная соль. Случайная соль нужна для того, чтобы нельзя было радужную таблицу построить. Атака дней рождения — из другой оперы.

MD5 и какие-либо другие одиночные хэши не стоит использовать даже с солью — при известном хэше и соли 8-мизначный пароль из букв верхнего и нижнего регистра, цифр и спецсимволов можно перебрать за несколько часов. Вместо этого современные системы используют PBKDF2 с большим количеством итераций для сверки пароля.
Не совсем понимаю, причём тут атака дней рождения
В том и дело, что ни причём. Но это единственная атака, которую практически можно провести на MD5. Кроме перебора, конечно.

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

Какого года сайт?
Лет 15-17 назад мало кто заморачивался хешированиями и соленьями.
Открытые пароли в базах лежали в каждой первой базе, которую я видел ^_^
Свежий сайт, работает на PHP 7.0 минимум (из-за синтаксического сахара не запустишь на более ранней версии). Просто вот такие разработчики с таким вот опытом…
Удивительно, но я сейчас обнаружил, что один питоняшный сайт, который был запущен в декабре 2017, держит все пароли юзеров в открытом виде, кроме двух — админских.
Чудеса в решете.
Если это «питоняшная» Django, то пароль админа наверняка ставился через:

$ python manage.py createsuperuser

А для пользователей через:

user.password = "..."
user.save()
а ведь они были так близки к
user.set_password("...")
user.save()

интересно, что их остановило… :)
Джанга, да.
Так что проблема внезапно актуальна, несмотря на крутость фреймворков :/

Хотелось бы еще почитать про аутентификацию на основе утверждений (claims-based).

Аутентификация — это проверка вашей личности. Когда вы входите в приложение с именем и паролем, вы аутентифицируетесь.
Пароль в примере — способ разрешить дальнейшее использование аккаунта, идентификатором которого выступает имя. Аутентификация — проверка на подлинность, на соответствие заявленному. Проверку прав на доступ к аккаунту можно считать подвидом авторизации. Аутентификацию можно считать неотъемлемой частью, обязательным шагом процедуры авторизации. То есть «не путайте» не должно превратиться в «основательно разделяйте».
А еще можно новомодным блокчейном аутентификацию делать)
запарная это штука. пока простое решение есть у emercoin, но там авторизация по сертификату. но для его получения нужен кошелёк и прочие проблемы
Про минусы токенов то не написали: невозможно инвалидировать принудительно, и возросший оверхед при запросах.
Не очень понятно про возросший оверхед — сессию вы проверяли бесплатно? Поясните, пожалуйста. Инвалидация — да, болячка известная
Понимаю что статья для новичков и вообще является перводом, но тут явно не хватает описания минусов всех описанных методов помимо аутентификации по сессии (а они есть и еще огого какие). Кроме того можно было бы хотя бы перечислить некоторые менее очевидные методы (тот же kerberos, к примеру).
Странно что не упомянули такую вещь как PAKE, которая позволяет аутентифицировать пользователя не передавая его пароль на сервер, тем самым исключая возможность «логирования» данного пароля сервером в открытом виде.
UFO landed and left these words here

Спасибо, актуально! Как раз ночью сдал контрольную на coursera.org, по аутентификации и авторизации в node.js через токены и пасспорт и думал что-нибудь почитать ещё по теме)

Автор, большое тебе спасибо! Я давно искал что-то вроде JWT, но почему-то Гугл для авторизации без состояния советовал только отдельные сервера для аутентификации, типо SAML и oAuth. Единственное о чем я думаю, секрет должен генерироваться раз в какой-то промежуток времени, иначе если его подобрали, то вся система скомпрометирована.

В статье приравниваются понятия аутентификации на основе кук и на основе сессий. Но ничто не мешает положить тот же токен в куку и не создавать сессии на сервере.

В статье так и написано.
"Токен хранится на клиентской стороне, чаще всего в локальном хранилище, но может лежать и в хранилище сессий или кук."

Тренировался тут на кошках… токены для gmail и vk.com благополучно были доставлены на другую машину путем экспорта-импорта кук. На свежей машине для тех же свежеавторизованных gmail и vk.com в локальном хранилище ничего. Возникает подозрение, что токены ОБЫЧНО хранятся в куках, но МОГУТ лежать и в локальном хранилище. Т.е. все наоборот. Какой все-таки best practice в этом вопросе?

Или я вас не пойму, или вы запутались.
Сессионный токен это просто идентификатор без полезной нагрузки, просто случайно сгенерированная строка. Такой токен нет особого смысла держать в веб-хранилище. Он передается на сервер через куки, а потом сравнивается с имеющимися токенами в БД.


Токен JWT это зашифрованные данные. Они могут занимать большой размер и поэтому в куки могут не поместится, тут на помощь приходит веб-хранилище. Поскольку доступ к этому хранилищу осуществляется через js, то такой подход актуален, в основном, для SPA.

Однозначно я запутался -поэтому и спрашиваю. Тогда уточнение gmail и vk вообще используют JWT для авторизации? А кто из больших использует? Хочется в chrome dev tool на него «живьем» посмотреть.
И правильно ли я понимаю, что если мы используем jwt — мы должны его при каждом запросе передавать на сервер? или есть нюансы?
Токен jwt содержит ваш userid и все права котопые вам нужны. Типа «Пользователь 17, Администратор рубрики 1, администратор рубрики 7». Эту строку сервер генерирует и подписывает своим сертификатом и отдает вам. Пои последующих запросах вы только отдаете эту строку серверу, и если сервер убедился что строка подписана его сертификатом то он доверяет указанным правам сразу, не проверяя ваши права ни по каким своим базам.
Токен jwt можно использовать как вам вздумается. Например если у вас токен большой то чтобы его не гонять, можно его хранить в webstorage и токеном получать классическую сессионную куку на сервере и гонять уже ее. В таком случае плюс в экономии траффика и возможности сделать короткую сессию чтобы базу не забивать сессиями годовалой давности.

Недостаток у токена один: если кто то отберет у вас права, то в токене они останутся и сервер будет доверять токену, а не обновленным правам (потому что токен валиден). Чтобы этого избежать придется городить костыль и проверять права при каждом запросе, и jwt перестает отличаться от простых сессионных куки. Можно конечно сделать красивый костыль — например при смене прав куда-то писать что для такого пользователя все токены старше х считать недействительными, и еще хранить дату выдачи в токене, и еще проверять при каждом запросе эту базу, вместо перечитывания всех прав
Ну и все-таки у кого из известных сервис провайдеров можно живьем jwt увидеть? Хоть в хранилище, хоть в куках…

"Кроме того, раз вы можете хранить внутри JWT дополнительные данные вроде пользовательских разрешений, то можете сэкономить и дополнительные обращения поисковые запросы на получение и обработку данных."


Ага, а если нам нужно изменить пользовательские разрешения?

Всегда можно выдать пользователю новый токен

Ну на сервере мы токен не храним. Когда пользователь к нам прийдёт с токеном, откуда мы будем знать что для него нужно выдать новый токен? Из БД?

Могу предположить что надо сравнивать дату выдачи токена и дату изменения записи пользователя… И еще в этом момент сразу отзывать старый токен. А то будет потом ходить один пользователь с разными токенами, разными правами скажем с разных машин и жаловаться что «все глючит».
Забавный момент в переводе
Оригинал
Another example you might be familiar with is the 2-step verification of Google, facebook etc.

Перевод
Другой знакомый пример — двухфакторная аутентификация Mail.Ru, Google, Facebook и т. д.


Забавно (а скорее печально) тут то, что это — знакомый пример двухшаговой аутентификации (2-step verification — 2SV), которую часто ошибочно считают двухфакторной (2FV). И такая ошибка в переводе более существенна, чем безобидное расширение списка компаний.

ГОСТ на эту тему сейчас только разрабатывается, но есть же NIST SP 800-63b, где про это очень хорошо написано. И Mail.ru стоило бы более грамотно подходить к этому вопросу, в переводах в том числе.
Согласен, это пожалуй более важная ошибка, чем самопиар мэйлру.
Для тех, кто не в теме:
Двухшаговая аутентификация отличается от двухфакторной тем, что при двухфакторной вы не знаете результат проверки первого фактора, пока не введете оба фактора. Результат для вас будет либо успех (оба фактора верны) либо неудача (как минимум один из факторов неверный). В двухшаговой вы сначала узнаете, что успешно ввели первый фактор и только после этого вам предлагается ввести второй фактор. То есть тут есть возможность провести атаку сначала на один фактор, а потом проводить атаку на второй. Это зачастую упрощает задачу злоумышленника и по праву считается менее защищенным способом аутентификации.
Авторизация — это проверка наличия у вас доступа к чему-либо. Это может быть набор разрешений на какие-то действия.

Авторизация — это НЕ проверка наличия у вас доступа к чему-либо.
Авторизация — это процедура предоставления субъекту определённых прав.
В литературе часто встречается, что авторизация состоит из двух частей — аутентификации (проверки наличия прав), а затем, по результатам, предоставление этих прав или отказ в них.
Так что это вопрос базисной терминологии.
Ну вот, вы всё запутали :) Aутентификация — проверка того, что идетифицированный субъект, есть тот за кого себя выдает. До прав еще не дошли. Контроль доступа (access approval, access control или enforcement) проверка при действии субъекта на наличие у него прав на это действие, то что часто и, не совсем правильно, называют авторизацией по-русски.
Поддерживаю, проблема размытости термина — авторизация это и процесс, и результат процесса.
Если процесс, то включает в себя аутентификацию.
Если результат, то отказ или отсутствие отказа, причем отсутствие отказа можно назвать предоставлением права.
Первый раз вижу такой определение авторизации.
На мой взгляд незаслуженно не рассмотрен вариант аутентификации на основе X.509.
В случае с Google этот сервис называется Google Accounts.
ЕМНИП сервис называется Google Maia, или что-то вроде того.
Since JWT are signed and encoded only, and since JWT are not encrypted, JWT do not guarantee any security for sensitive data.
Есть такое, но это не особая проблема т.к. и так JWT должен пересылаться поверх https. Если нужно большее, то есть JWE (JSON Web Encryption).

Ну, обычно JWT содержит данные о пользователе. Вряд ли они являются секретными для него самого.


Кроме того, ваша цитата противоречит RFC 7519:


JWTs represent a set of claims as a JSON object that is encoded in a JWS and/or JWE structure.
— пункт 3. JSON Web Token (JWT) Overview

То есть JWT может быть как подписанным, так и зашифрованным.

И ещё один, менее (пока) популярный (и доступный только на устройствах Apple) метод беспарольной аутентификации: использовать Touch ID для аутентификации по отпечаткам пальцев.

На адроиде СБОЛ использует биометрию вместо ввода пинкода, так что это не только Эпл.
image

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


Ведь тот же беспарольный доступ с помощью одноразового токена или логин через соц. сети передает на сервер некий токен по клику по ссылке в письме или при редиректе с соц сети обратно на сайт проекта.


Далее сервер проверяет этот токен (в базе, oAuth, да это может быть тот же JWT). И в случае успеха для последующих запросов клиент уже будет передавать нечто внутреннее для системы с помощью куки или заголовка.


Так что я бы разбил методы на две группы — что мы используем для логина и что мы используем для подтверждения аутентификации для последующих запросов.

Only those users with full accounts are able to leave comments. Log in, please.