LINUX.ORG.RU

Токены авторизации в веб-приложениях

 


0

2

Вот чисто интуитивно есть два варианта реализации авторизации в веб-приложении:

1) Храним сессию в БД, юзеру выдаём после успешного ввода логина-пароля ключ (разумеется, ключ должен быть длинным и случайным, чтобы исключить угадывание). При каждом запросе вытаскиваем из БД сессию по ключу и проверяем, что сессия валидна.

2) Шифруем важные для нас данные об учётке и отдаем юзеру в качестве токена. При каждом запросе дешифруем токен и если получается осмысленный результат, то считаем юзера авторизованным, в БД не ходим. Так как ключ шифрования недоступен юзеру, то это тоже надёжно.

Второй вариант с точки зрения производительности выгоднее первого, так как мы уменьшаем I/O (плюс общую БД тяжелее масштабировать, чем инстансы приложения, так что жрать CPU лучше, чем жрать I/O).

Но возникает одно НО. На многих сайтах есть функция «выйти со всех устройств» или даже список сессий и возможность кикнуть любую сессию. Это полезно в случае, если нечаянно залогинился на чужом компьютере или если тебя поломали, но ты успел сменить пароль.

Как это реализовано в первом варианте понятно - простое удаление сессии из БД. Как это реализовывать во втором варианте - непонятно.

Возникает вопрос, как устроены крупные сервисы типа всяких гуглов. Если посмотреть запросы, которые шлёт сайт гугла, там будут JWT токены, которые предполагают второй вариант (но, конечно, не гарантируют). Но у гугла есть опция, например, при смене пароля выбить все сессии во всех браузерах. Значит ли это, что гугл на каждый запрос к своему API дёргает БД сессий и JWT там только для красоты или каких-нибудь неважных сервисов.

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

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

★★★★★

Последнее исправление: KivApple (всего исправлений: 1)

Почитай что такое и как работает oAuth2. Стратегий хранений токенов на сервисах авторизаций много и бд и всякие монги и гибридные.

vtVitus ★★★★★
()

Никто в здравом уме сессии без хранения где-то у себя их списка делать не будет. Да и хранятся не только сами сессии (на чтение) а ещё и пишется лог всех действий юзера (т.е. запись, и без такого лога никакой крупный сервис не обойдётся) и нагрузка от хранения сессии на этом фоне вообще незначительна.

А мелкие сервисы с онлайном пара человек в сутки могут хранить как угодно и экономить на этом копейку в день.

Для кнопки «выйти со всех устройств» кстати сессия не в базе не мешает, хотя базу придётся использовать для хранения времени последнего нажатия этой кнопки, так что твоя оптимизация «не трогать базу вообще» не прокатит, да.

firkax ★★★★★
()

Но возникает одно НО. На многих сайтах есть функция «выйти со всех устройств» или даже список сессий и возможность кикнуть любую сессию. Это полезно в случае, если нечаянно залогинился на чужом компьютере или если тебя поломали, но ты успел сменить пароль.

Не только, есть ещё как минимум один момент — если ты обновил какие-то важные данные юзера (например, удалил у него роль admin), в уже существующих токенах данные останутся протухшими.

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

(Лаг «в несколько часов» — это security nightmare в данном случае. Stateless JWT должны быть короткоживущими).

В данном контексте в терминах JWT «токен второго типа» обычно называют access token, а «токен первого типа» — refresh token. Чтобы сделать это без лага, тебе всё равно понадобится какое-то хранилище (например, in-memory или Redis), в которое тебе придётся лезть на каждый запрос аутентификации.

Например, юзер нажал «выйти со всех устройств»/«выйти со всех устройств, кроме этого». На сервере:

  1. Инвалидируешь в БД все активные refresh-токены (кроме текущего в случае «выйти со всех устройств, кроме этого»).
  2. Пишешь в Redis ID юзера и текущее время, ставишь TTL этой записи равным времени жизни access-токена.
  3. При запросах аутентификации проверяешь для access-токена наличие записи юзер/таймпстамп в Редисе из прошлого пункта. Если эта запись есть, и таймпстамп больше времени создания токена, отклоняешь токен.

Если же ты обновил данные юзера, и тебе нужно просто инвалидировать access-токены (чтобы получить новые/актуальные), то алгоритм тот же, просто пропускаешь первый пункт.

Также ещё в том же Redis хорошо бы хранить blacklist протухших access-токенов, с тем же TTL, равным времени access-токена, это позволит реализовать функцию «выйти с текущего устройства». Да, тебе тоже его надо будет проверять на каждом запросе аутентификации.

Всё это несколько нивелирует преимущества по производительности и I/O, которые ты описал, но: 1) Redis обычно быстрее и легче, чем традиционная RDBMS; 2) данных там будет гораздо меньше, чем в таблице sessions (т.к. мы храним там только протухшие access-токены и записи user-id,current-timestamp, плюс данные там регулярно очищаются за счёт того, что у каждой записи TTL равен времени жизни access-токена); 3) эти данные не так страшно потерять; 4) Redis достаточно просто масштабировать. Так что это всё равно должно быть быстрее, чем хранить сессии в традиционной RDBMS.

Из минусов такого — тебе это надо запилить, придусмотреть все граничные случаи (а они наверняка ещё есть, я написал только про то, что пришло в голову сразу), покрыть тестами. Реализация получается более сложная в итоге. Я бы тебе рекомендовал сначала убедиться, что один запрос в БД для получения сессии на каждый запрос к твоему сервису действительно является узким местом в твоём приложении.

И вот, почитай ещё:

theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 3)

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

vbr ★★★★
()

Еще не факт что там будет выгоднее с точки зрения производительности, с учетом криптографии в jwt и in memory k/w баз данных специально для хранения сессий. Кстати k/w базы без требования транзакционности отлично масштабируются.

Для токенов надо делать список отозванных токенов и туда лезть. Лаг eta nekulturna.

Вообще мне кажется что вся эта ботва со стандартизироваными токенами это была мусорская идея, для единообразия бекдоров. Типа у каждого сервиса берешь их private key и набор полей и генеришь себе доступ к чему угодно без необходимости контачить напрямую с инженерами каждый раз чтобы дали доступ. Хорошо автоматизируется и не привлекает внимания, при правильной постановке работы инженеры про это даже и не узнают.

shimshimshim
()

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

ya-betmen ★★★★★
()
Ответ на: комментарий от theNamelessOne

Это можно сильно упростить, если нет требования инвалидировать access токены МГНОВЕННО. Можно просто инвалидировать refresh токены и любые сессии скоро закроются. На практике, если время жизни access токена не превышает 30-60 секунд, особой разницы не будет.

anonymous
()

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

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

Я делал примерно так - в токене хранил поле с хешем важных данных о юзере (права доступа, дата смены пароля, сколько раз просил разлогинить со всех устройств итд). Когда access-token протухает - проверяем хеш в рефреш токене, и если что-то поменялось то рефреш токен невалиден - пользователь разлогинен.

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

Но если токен живет минуту-две, то это скорее всего не критично (юзер может несколько часов узнавать что у него учетку угнали, и пара минут тут роли не сыграют).

OxiD ★★★★
()
Последнее исправление: OxiD (всего исправлений: 2)