Как стать автором
Обновить

Взгляд на JWT как на инструмент построения стройной архитектуры приложения

Уровень сложностиСредний
Время на прочтение11 мин
Количество просмотров7.7K
Всего голосов 11: ↑10 и ↓1+9
Комментарии35

Комментарии 35

НЛО прилетело и опубликовало эту надпись здесь

Есть ощущение, что вы прочитали статью очень сильно по диагонали. Время жизни токена вы задаете сами: у меня это, как правило, две недели. Про сумасшедшую нагрузку на базу я тоже не писал. Статья в принципе не об этом.

  1. в токене указываем два времени:

    1. до которого он действителен - +1 год

    2. после которого нужно выполнить верификацию валидности/забаненности итп

  2. on-demand периодически валидируем все токены, обновляя второе время в них (то есть перевыдавая Set_Cookie)


Вы точно мою статью пересказываете? Придумали какие-то годы и минуты и перевалидацию всех токенов.

НЛО прилетело и опубликовало эту надпись здесь

Прошу прощения. Я подумал, что Вы критикуете меня в форме искаженного пересказа моего текста.) Теперь, когда Вы указали, что это Вы описали свое, третье, решение, я взглянул на Ваш комментарий по-новому. Мой косяк.)

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

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

Спасибо большое, что поделились своим решением.)

НЛО прилетело и опубликовало эту надпись здесь

Возможно, я просто опять Вас не так понял и мы снова говорим про разное.) Обычная схема какая: Выдается два токена: собственно JWT и refresh_token. Когда срок жизни JWT истекает, фронт (или иной клиент) автоматически запрашивает новый токен по refresh токену.

Предполагается, что если злоумышленник перехватит JWT, то он сможет пользоваться им только до истечения его срока годности, а потом, не имея refresh токена, он потеряет доступ к приложению.

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

Кстати, а как насчёт привязки токена к IP-адресу? Плюс для безопасности можно проверять данные юзерагента.

JWT, как правило, выдаются людям, а люди редко имеют статические IP-адреса. Технически-то легко сделать: добавляете IP в payload токена и проверяете при каждом запросе. Только смысл это будет иметь, если клиентами вашего приложения являются не люди, а другие приложения, работающие на статических IP-адресах.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

Ваши комментарии еще поосмысляю, спасибо, что пишите их. Пока пару моментов могу сказать:

Перевыпуск токена на фронте можно сделать без слежки за временем, а просто как реакцию на 401 ответ. Фронты на это спокойно смотрят.)

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

То есть, здесь надо будет более тонкую логику какую-то делать. Пока еще соображаю.)

НЛО прилетело и опубликовало эту надпись здесь

Отвечать новым токеном на запрос о вступлении в группу - хорошая идея! Спасибо!

Единственно, если фронт по каким-то причинам не сохранит полученный JWT, то будет ломиться со старым токеном. Так что, уйти таким способом от проверки актуальности токена, как я вначале подумал, не получится, увы.

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

НЛО прилетело и опубликовало эту надпись здесь

Да, только если делать set-cookie в ответе на каждый запрос, то мы возвращаемся к началу: придется каждый раз генерировать JWT, лазая в базу.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

Аналогично я не понял тезис о бешеной нагрузки на базу. А для чего вообще такая база нужна тогда, если не выдерживает нагрузку? Проверка подлинности должна быть на стороне сервера. Если пользователь забанен, значит должен быть забанен здесь и сейчас.

НЛО прилетело и опубликовало эту надпись здесь

Если в более-менее сложном проекте запихать в jwt всю информацию, необходимую хотя бы для допуска/недопуска юзера к функционалу и данным, может получится много. Прям килобайты, а то и десятки. Гонять такое на каждом запросе - ну такое себе.

На самом деле, никакой трагедии в походе в базу по первичному ключу нет. С этим и обычные бд справляются, а если кажется что нет, то есть всякие редиски с монгами и тарантулами. В этом случае в jwt будет лежать только id юзера и его пропуска. Зато - снимается проблема с логаутами и банами. А вот подпись jwt позволит ваф (фаерволлу) отсекать дудосеров ещё до похода в базу.

На самом деле, никакой трагедии в походе в базу по первичному ключу нет.

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

Прям килобайты, а то и десятки

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

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

А про главное собственно и не сказали: что в этом payload хранить, чтоб было достаточно принимать решение

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

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

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

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

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

Образуются всякие роли

Попробуйте мыслить атрибутами, а не ролями.

каждый сервис должен их держать актуальными

Обратите внимание на тот абзац, где я писал про вынос всего, что касается Security, в отдельный composer пакет.

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

IMHO
Смешивать Security с Domain как-то попахивает. Тем более в таком порядке.
1. Symfony не интересует ваш Domain.
2. По определению у каждого микросервиса есть своя база данных. Т.е. только у одного есть (может/должна быть) информация о юзерах
3. Понятие роли определяется в контексте определенного микросервиса

В общем, если вы первым шагом запрашиваете Domain/User, то вы нарушаете Single Responsibility

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

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

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

SecurityUser желательно отделить от SecurityUser с точки зрения архитектуры. Если User хранится в бд, то он является элементом persistence layer. В тоже время SecurityUser логично поместить в тот слой, где он создаётся в приложении - в facade layer, то есть в самый верхний слой в котором находятся endpoint как монолита, так и микросервиса. И не будет никакого смешения Security с Domain.

Тут зависит от того, что вы понимаете под принадлежностью юзера к persistence layer. Если у вас на класс User завязана какая-то бизнес-логика, и вы, например, разметили его доктриновскими аннотациями, то это - тоже смешение инфраструктурного и доменного слоев: вы добавили в доменную сущность метаданные о том, как эта сущность хранится.

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

При необходимости можно поместить User в слой домена. Главное, чтобы SecurityUser был в другом слое - не слое бизнес-логики.

Да, все верно.)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации