Pull to refresh

Comments 42

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

не храня их в базе данных
при смене пароля запоминаем время

Запоминаем и храним где? На бумажке?

я сказал: "инвалидировать токены, не храня их (т.е. токены) в базе"
время все еще надо хранить, токены не надо


автор переживал, что понадобится мега-база для хранения токенов, но достаточно хранить одно поле с датой
это делает ситуацию проще, но не отменяет необходимости лазить куда-то в хранилище, о чем я также сказал

Всё-равно понадобится мощная БД, которая будет в памяти держать все токены и кучу аттрибутов к ним — время смены пароля, время инвалидации, кто и когда инвалидировал итд. И не просто в памяти держать все это будет, но и отдавать, и перезаписывать данные, искать все старые токены и удалять их. И оп, поздравляю, вы изобрели кластер из memcached! И все преимущества self-contained токенов коту под хвост.
И все преимущества self-contained токенов коту под хвост.

из 100 запросов к серверу за сессию пользователя за рефрешем токенов будет только один-два запроса. Более того для авторизации не нужно делать лишних запросов. так что профит все равно будет. Для подавляющего большинства этого более чем хватает. Не говоря уже о том что это просто удобно.

В том-то и фишка, что при таком подходе сами токены (как и их атрибуты) хранить не надо.


Токен "почти самодостаточный", проверка валидности требует хранения ровно одного дополнительного значения для каждого пользователя. Время смены пароля (или время инвалидации всех ранее выданных токенов, как больше нравится) – это атрибут пользователя, а не токена. Других значений не требуется.


Причем меняться это значение будет редко и актуализировать его вполне позволительно с некоторой задержкой (до секунд, по крайней мере). Значит, куда больше простора для выбора технического решения, все-таки распространять по кластеру ровно одно значение для каждой смены пароля значительно проще, чем работать с полновесными списками инвалидации.


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


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

>>>Есть простой способ инвалидировать токены, не храня их в базе данных. Зашиваем в токен время выдачи,

Да, но ведь это и есть self-contained токен. Так он и работает. В базу обращаться не надо — но и инвалидировать такой токен нельзя.

Так что при смене пароля/блокировке пользователя — старый self-contained accesss токен еще будет рабочим некоторое время.

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

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

Тем не менее, это значительно проще ведения списков инвалидации.
Кроме того, полноценная проверка валидности самодостаточного токена, кажется, в принципе невозможна без обращения в базу (как иначе убедиться, что его не отозвали?).
Это всего лишь более простой способ делать такую проверку.

>>>полноценная проверка валидности самодостаточного токена, кажется, в принципе невозможна без обращения в базу (как иначе убедиться, что его не отозвали?).

А никак…
Принимаем, что некоторое время после инвалидации (вызванной, например, сменой пароля) старые токены все еще работают.

Это — безусловно — ослабление безопасности — но не катастрофа.
На уровне гугла, твиттера или фейсбука это будут миллионы запросов в распределённые БД?

А какой шанс взлома тонена и увеличения себе прав?

Кажется тут явно смешали мух с котлетами.
Вижу описание преимуществ self-contained токена, но как это вообще связано с refresh? Просто представьте, что нету refresh-токена, но есть sef-contained — ничего ведь не изменится, всё также будет возможность локальной валидации и раз в час необходимость сходить и получить полную информацию о пользователе.

Настоящий бонус refresh-токена это возможность обновления access-token без знания и необходимости хранения логина и пароля пользователя. Именно поэтому refresh-токены выдаются при password-авторизации и не выдаются при client_credentials, там они бессмысленны.
Типичный пример — пользователь вводит логин и пароль, мы его даже видим на стороне backend и отправляем куда-то в сервер авторизации, но у себя храним только access-токен и refresh-токен, никаких паролей хранить не надо да и нельзя. Когда access-токен протух, просто отправляем на сервер авторизации refresh-токен.

… или отправляем протухший access токен в метод для выдачи нового токена, и по нему получаем новый (с ограничением по времени, которое бы вы использовали для рефреш-токена). Так все таки, зачем генерировать и хранить второй токен, когда задача решается одним?

Так и не понял «Так зачем же все таки нужны Refresh токены в OAuth?» из вашей статьи. Вторая половина статьи вообще не понятно причём тут.

Из практики, у того же гугла — рефреш токен выдаётся только при дополнительном запросе (access type — offline), если не запросить — значит access token будет валиден только час. Нет рефреш токена — значит пользователь дал доступ к своим данным только на один час. Мне кажется это достаточно логичным, но возможно это только фишка гугла, не стандартное поведение?..

Это не так, пользователь уже дал доступ к своим данным "навсегда", единственное что сервису надо делать когда истекает 1час жизни access_token — это организовать запрос по получению access token, — если пользователь залогинен в гугле, и пользователь уже дал свое согласие (consent) — выдача access_token происходит в серии редиректов автоматически. Единственное принципиальное требование — надо чтобы пользовательский браузер был онлайн. Т.е. токен можно обновить только при непосредственном активном участии браузера пользователя ( при этом — почти прозрачно для пользователя т.к. редиректы). В Implicit flow — абсолютно прозрачно через js.


Оффлайн доступ подразумевает что сервис может обновить access token самостоятельно, без вовлечения пользователя в процесс обмена. Что более рисковано потому что если refresh токен утекает — любой знающий токен сможет им воспользоваться для обновления — вся остальная информация о деталях flow и client id — публична.


Это все было очень "гибко" описано в стандарте OAuth2 вот тут 1.5 Refresh Token.


Issuing a refresh token is optional at the discretion of the authorization server.

Настолько гибко что каждый сервис имплементировал это как они сами хотят и был зоопарк мнений и походов (без единого стандарта, как в общем и в остальных вопросах, уж очень гибкий Oauth2 получился).


В итоге в OpenID Connect сказали — хватит разврата и "закопали стюардессу" — т.е. добавили более строгое описание в свой стандарт — 11. Offline Access
тут уже и четко прописано какой скоуп запрашивать, как сервер должен себя вести и все такое.


Оно конечно не то что бы сильно упростило весь разврат вокруг любимых AuthN & AuthZ, но по крайней мере определило — куда слать и какие есть ограничения по поведению...

Ну что значит не так? Если у меня истёк срок access_token — у меня два выхода, или иметь Refresh Token или попросить пользователя снова выдать доступ. Если у меня нет ни токена (не запросил/не дали) ни другого (при сервер-сервер вообще нет понятия «браузер пользователя») — мой access_token перестал работать и я сижу курю, никаких там «пользователь уже дал доступ к своим данным „навсегда“, сервер мне больше не даст никаких данных.

Сервер-сервер сам по себе не случается, должен быть инициатор поведения — пользователь, который говорит — хочу пользоваться ВебсайтомХ (или приложением которое поможет мне с gmail), не хочу регистрироваться, хочу залогиниться с гуглом, но не хочу давать им свой пароль от гугла. Вот из-за этого конфликта в мозгу пользователя и были созданы протоколы — openId1.0, openId2.0 Oauth1.o Oauth2.0, Open Id connect. Первые три умерли, четвертый выжил но его пользовали не по делу — вместо задуманной авторизации делали аутентификацию пользователей (потому что аутентификацию никто адекватно не сделал). В итоге OIDC, который протокол аутентификации был сделан поверх протокола авторизации. И вроде как теперь покрывает обе А-ции.
Описанный у вас сервер сервер — да, второй раз не выдаст, ваше левое почтовое приложение при появлении пользователя скажет ему — не могу логиниться, давай меня заново авторизуй. Что пользователь сам по себе и рад сделать, ибо привык что периодически что-то может отвалиться.


На самом деле чтобы ответить на вопрос — "а зачем такой токен ?" надо понимать протоколы и модель злоумышленника. Т.е. сначала понимаете как можно с помощью современных атак сделать гадость от имени пользователя, если вдруг удастся заполучить его токен, а потом смотрите на протоколы и понимаете — вот такое ограничение в таком режиме — потому что иначе яваскрипт через xss может сделать такую вот каку и чтобы это не случилось — протокол не разрешает какую-то такую фичу...


PS простой момент — протокол не защищает от man in the middle ( это вам к Oauth1 c подписями), он просто снижает риск того что случайный МитМ получит меньше возможностей. Если МитМ постоянный — тут не важно какие токены — он вас поимеет в любом случае.

Как-то ваш комментарии слабо связаны с моими, видимо мы говорим о разных вещах. Сервер-сервер в моём понимании — «мой сервер общается с сервером гугла». Если барть в пример «помощник с gmail» — мой сервер должен иметь возможность получать почту с серверов гугла, чтоб выдать уведомление пользователю о новой почте, даже если эта почта пришла спустя 2 час после того как пользователь заходил на мой сайт (и имел возможность обновить токены или сделать что-либо ещё). То есть он один раз зарегался на моём сервисе, оставил токен(ы) и пошёл пить пиво, а мой сервер теперь шарится по его почте и ищет новые письма.

Перечитал тред. "Не совсем так" относилось только к валидности access токена — его можно обновить если пользователь еще активный и у вашего сервиса есть возможность направить пользователя на переавторизацию. Такая авторизация второй раз уже может произойти без всяких consent окошек, т.е. втихую. Или рефреш токеном.


В остальном — все правильно, так и используется.


В период существования только стандарта oauth2 — фишка с offline и refresh_token — была на усмотрение каждого провайдера. C принятием OIDC — это теперь часть стандарта OIDC Core — это ответ на изначальный вопрос — "у всех так или только у гугла".


Про сервер-сервер — лишнее пояснение на самом деле потому что ни один из сценариев протокола не предусматривает полное отсутствие пользователя. Всегда есть пользователь с браузером\клиентом понимающим http, хотя бы в начале протокола, чтобы дать согласие или инициировать первый шаг — перейти по ссылке. Остальные вещи — это просто увлекся описанием...

Ну до тех пор пока мы можем куда-то зачем-то отправить пользователя — вообще не важно есть ли у нас какие-то токены или нет :) Надо сделать запрос — перекинули пользователя, получили ответ :) Про «без окошек» оно понятно, так и делается, в принципе.

Есть куча способов как обойтить без http у пользователя, например взять токены из базы :)

Идея refresh_token в том, что он одноразовый. Представим ситуацию, что во время аутентификации, злоумышленнику удалось перехватить твой rafresh и access токен. Он сможет выполнять действия от твоего имени в течении срока жизни access_token. После этого срока ему нужно будет поменять refresh_token на новую пару access и refresh токенов. При обмене, старый refresh_token инвалидируется и им не сможет воспользоваться ни злоумышленник, ни пользователь.


Таким образом, возможны две ситуации:


  1. Первым обменял refresh_token злоумышленник. При попытке обменять, пользователь получит сообщение о том, что его refresh_token не действительный. Он авторизуется заново по логину и паролю и получит новую пару refresh и access токенов. В результате, refresh токен злоумышленника станет более не действительным и он не сможет выполнять действия от имени пользователя.
  2. Первым обменял refresh_tokn пользователь. В результате, refresh токен злоумышленника станет более не действительным и он не сможет выполнять действия от имени пользователя.

Это не совсем так — если случился случай 2 — все верно, пользователь обменял, злоумышленник в дураках. Если же злоумышленник адекватный — он обменяет пару сразу, получив монопольный доступ ( потому что у него будет и новый валидный рефреш и валидный аксесс токен). Пользователь же — в лучшем случае будет выкинут из системы (если система при обновлении токена инвалидирует старый аксес токен немедленно), в худшем же (если access_token — self contained и он не инвалидируется а просто истекает) — продолжит пользоваться пока аксесс токеном и спустя время жизни этого токена — будет разлогинен или сервис перестанет работать как надо…


На что 99% пользователей поругают криворуких разработчиков и залогинятся еще раз ( потому что мы привыкли что веб кривой, перезайти — хороший способ пофиксить проблему...). В итоге сервис выдаст новую пару аксесс и рефреша для того же самого девайса и забудет про это (или вообще если нет возможности верифицировать deviceId).


Только очень сложные и умные сервисы ( читай — много денег вложено в эти вопросы) — будут приставать к пользователю с табличками что вот кто-то логинился сюда из неизвестной локации, с валидным токеном (см пункт 1 — у злоумышленника валидная и обновляемая пара) и может вы нам подскажете — вы ли это были, а если нет — мы тот рефреш и аксесс заблокируем по вашему желанию...


Не то что бы я против refresh токенов, но надо понимать особенности их использования и связанные с этим риски...

Расскажите, пожалуйста, таким сервисам как Bitrix24 что refresh_token должен быть одноразовым.
По спецификации это не обязательно.

Система с refresh/access токенами крайне удобна, если ваша архитектура состоит из микро-сервисов.


Например, у вас есть облако тип AWS, которое состоит из микросервисов типа хранилища (S3), сервиса управления серверами, бэкапы, итд.


Вы, конечно, можете сделать все это одним большим монстро-приложением, но рано или поздно вам прийдется делить его на части. И вот тут вариантов у вас будет не много — вам прийдется либо дублировать код с БД и аутентификацией, либо выносить его в отдельный пакет. О безопасности тут уже и говорить нечего — каждый модуль будет требовать доступа к БД с пользовательскими сессиями.


Access/refresh токены элегантно решают эту проблему. У вас есть один сервер аутентификации, который проверяет в БД логин/пароль, и выдает вам какой-нибудь jwt токен с информацией о юзере/правах, подписанный приватным ключем сервера аутентификации. Такой токен нельзя отозвать, но зато у него крайне короткий срок жизни, например 5 или 10 минут. Вместе с ним выдается refresh токен, который используется пользователем для "обновления" access токена через тот же сервер аутентификации.


Собственно, задача решена. Все, что потребуется остальным микросервисам — проверить токен с публичным ключом и убедиться, что юзер имеет доступ к заданному. Никакой работы с БД, никакой интеграции с сервисом аутентификации, единственная зависимость — это библиотека для работы с токенами (JWT).
Вы даже можете использовать разные ЯП/стэки технологий для написания ваших микросервисов (например, сервис чата с техподдержкой вы можете написать хоть на C++, если так уж хочется) — все что нужно это возможность расшифровать токен и проверить его валидность.


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


Единственный минус такой системы — это невозможность мгновенно отозвать access токен. Но это как правило и не нужно, т.к. срок жизни такого токена крайне короткий (несколько минут).

>>>Единственный минус такой системы — это невозможность мгновенно отозвать access токен. Но это как правило и не нужно, т.к. срок жизни такого токена крайне короткий (несколько минут).

Абсолютно верно. Интересно проверить, у кого из «больших дядь» (гугло-фейсбуков) возможно мгновенно отозвать (инвалидировать) access токен.

Есть ли вообще такие?

Один способ — это иметь отдельную БД типа Redis в которой хранить исключительно список отозванных токенов. Таких токенов в реальности будет совсем немного, т.к. после даты истечения токена (максимум минут 10) его можно будет из этой БД удалить, соответственно нагрузка на такую БД будет меньше. Это, конечно, не идеальный вариант, но он гораздо лучше, чем "простая" модель аутентификации.

То есть я забанил спаммера, а он ещё час может творить свои непотребства? Классно оптимизировали.

Добро пожаловать в распределенный мир — не все сервера видят одно и то же состояние. Просто если это все мелко и быстро — пользователь не замечает рассинхронизации, а если масштабно — инвалидация кэша это больно

Протухающий каждые 5 секунд токен — тоже так себе оптимизация. Вообще, инвалидация кеша по таймеру — худшее, что только можно придумать. Лучше смотреть в сторону подписок. В данном случае — подписка на блеклист. При этом вовсе не обязательно хранить весь чёрный список локально — достаточно иметь достаточно большой фильтр блума, при ложноположительном срабатывании которого клиент просто идёт за новым токеном.

А вы тут сами себе отвечаете? self-contained token — хорошая оптимизация для good path. Т.е. можно токен при запросах валидировать и проверять без центральной синхронной базы. Это экономит поход за проверкой на каждый запрос пользователя = серьезно экономит ресурсы. Недостаток подхода — что отозвать такой токен тоже непросто. Можно не отзывать вообще и сделать небольшое окно жизни типа 10 минут. Понятно что в автоматическом режиме спама это не спасет, чем дальше уменьшаем окно — тем больше нагрузка за счет увеличения частоты перевыписывания токена. Ищем баланс и проглатываем риски.


Черный список — это та же проверка в базе, ну ее можно оптимизировать по памяти всякими свертками и временем жизни кэша (не имеет смысл держать записи отозваных токенов если текущее время уже после exp токена). Если он асинхронный — у вас та же проблема — спаммер может попасть на сервер с необновившимся списком, если синхронный = каждый отзыв токена вызывает волну обновлений и задержку работы валидного пользователя… И все равно на каждый запрос будете делать сверку с этим blacklist.


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


Так что подбираем комфортное окно и проглатываем риски...

Вы всё же почитайте про фильтр блума. Он позволяет держать локально в памяти сравнительно небольшой битовый массив и очень быстро проверять вхождение токена в список отозванных.

Это вполне может сосуществовать рядом с self-contained токенами. К примеру:


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

Все вроде бы счастливы, нет?

Оно только с ними и нужно. С чего вы вообще взяли, что я против самодостаточных токенов? :-) Я против инвалидации по времени.

Все зависит от средней сессии пользователя, от нефункциональных требований и т.д. То есть цифры вы подбираете исходя из нагрузок на сервер авторизации и требований по времени инвалидации сессии, что бы всем было удобно.


С другой стороны давайте рассмотрим вот этот вот кейс со спамом. Какова скорость реакции модераторов на спам? ну то есть сколько пройдет часов между первым логином спамера и его баном? Это же не в автоматическом режиме обычно происходит (мы не обсуждаем механизмы жалоб на контент и т.д.), а стало быть тайм лаг в 10-15 минут к примеру вполне себе допустим. Вот если вы делаете например трэйдинговую платформу и там нужна мгновенная инвалидация сессиий, ну тогда нам будет проще мидлварю замутить которая будет смотреть блэклист по юзерам.

С жалобами реакция может быть и в пределах минуты. Чем быстрее отреагируем, тем меньше людей увидят спам. И хорошо, если это просто спам, а не что-то незаконное — тогда чем больше людей увидят тем больше вероятность скандала и последующего закручивания гаек. А за лишние 10 минут группа спам-ботов может очень много дел натворить.

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

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


А за лишние 10 минут группа спам-ботов может очень много дел натворить.

блэклисты решают эту проблему. И тут как раз преимущество self-contained токенов. Мы можем хранить просто айдишки неугдных юзеров и не пускать их на уровне какого-нибудь балансера/мидлвари.

Это связано со временем жизни токена после его отзыва.


Именно об этом я и писал выше.

Если пользователь изменил пароль то используется спека OpenID Connect Session Management для прекращения сессии.
Если надо проверять референс токен на валидность — https://tools.ietf.org/html/rfc7662.

Refrsh используется ТОЛЬКО в сценарии при котором сервису необходим доступ к апи, к которому пользователь делегировал ему доступ, когда нет АКТИВНОЙ сессии пользователя.
И сессия пользователя != access_token и уж тем более refresh_token.

Если вы не разбираетесь в этом вопросе — не пишите статьи, которые могут побудить менее опытных разработчиков принимать неверные решения.

Вот список основных спек для OIDC: http://openid.net/connect/.
И для OAuth2: https://oauth.net/2/.

Sign up to leave a comment.

Articles

Change theme settings