LINUX.ORG.RU

Как правильно сделать аутентификацию для вебни

 ,


0

3

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

Суть такая: есть фронты, несколько, веб/мобилки/десктоп и т.д. Есть бэк в нескольких az, с общей базой и вот этим всем. Есть несколько миллионов юзеров (DAU на пару порядков ниже) и до 100 rps. Ну и есть всякие login with google/apple/shitbook, saml2, totp 2fa и прочие ужасы. Есть нынешняя система авторизации, которая за годы превратилась в цирк с кривой схемой и данными которые периодически вообще лежат в другом сервисе. И исправлять которую по трудозатратам плюс-минус как с нуля переписать

Изначальная идея: взять keycloak, прикрутить кастомную федерацию которая будет брать данные из старой системы и постепенно перетягивать юзеров к себе. Нынешняя система использует plain token и на каждый чих лезет в базу. Собственно идея была взять jwks, где-нибудь в редисе/пабсабе хранить revocation list с ex в пару часов и тупо реплицировать где только можно (даже под нагрузкой если там только revocation он много не сожрет) и гонять в токене все что до этого шло из базы (user ID, почта, группы).

С чем столкнулся: с кучей статей в сети на тему что если дать фронту jwt напрямую, то будут CSRF, XSS, БТИ и другие страшные аббревиатуры. Начал копать что предлагают, и обычно все сводится к тому чтоб сделать микробэк который будет брать plain token от фронта, менять его на jwt и уже самостоятельно следить за lifecycle.

Ну и собственно вопрос: как сделать нормально? Я знаю что keycloak разделяет session token и access token, можно ли тупо использовать его и воткнуть обмен сессии на access через подзапрос в nginx ingress? Можно ли забить и тупо вызывать introspection на каждый запрос? Но в этих случаях все 100 рпс будут дружно долбить в keycloak. Или поскольку revocation в нашем случае крайне редкая штука (типа explicit logout или принудительного отзыва токенов) и там одномоментно будет от силы пара сотен записей - правда ли все будет так грустно как пишут, и нельзя ли просто загнать revocation в пабсаб с exp в пару часов и тупо слушать его тем же nginx или напрямую приложением для локального кэша отозванных токенов? Или можно сделать что-то в этом духе прямо на ингрессе? Или присобачить таки sidecar который будет это делать и использовать auth request до него?

Ссылки, маны, советы welcome



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

все сводится к тому чтоб сделать микробэк который будет брать plain token от фронта, менять его на jwt и уже самостоятельно следить за lifecycle.

Называется BFF (Backend For Frontend).

Использование BFF не избавляет от CSRF, XSS и прочего (https://habr.com/ru/articles/880544/), но он действительно затрудняет реализацию части атак (например, open redirect).

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

как сделать нормально?

Сделать нормально что? Авторизацию? Аутентификацию?

У вас напутано, спрашиваете про аутентификацию, но зачем-то упоминаете revocation list, access_token (которые относятся к авторизации, а не к аутентификации), session_token (который не относится ни к аутентификации, ни к авторизации).

Можно начать с RFC 6749, RFC 9700, чтобы почитать про авторизацию. Если нужна работа с сессиями, тогда надо смотреть в сторону OpenID Connect (в частности https://openid.net/specs/openid-connect-session-1_0.html).

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

ma1uta
()
Ответ на: комментарий от ma1uta

У вас напутано, спрашиваете про аутентификацию, но зачем-то упоминаете revocation list, access_token (которые относятся к авторизации, а не к аутентификации), session_token (который не относится ни к аутентификации, ни к авторизации).

Потому и написал «и базовую авторизацию». На уровне «токен ещё жив».

Rfc пойду почитаю, спасибо

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

Непонятно зачем вам геморрой с токенами при 100 рпс, это же совсем детская нагрузка.

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

Вот чем вы сами себе обосновываете плюсы от перехода на сессии в токенах?

shimshimshim
()
Ответ на: комментарий от shimshimshim

Можно не гонять в хранилище, а слушать очередь/пабсаб с максимальным сроком жизни сообщения в expiration токена. При запуске тупо прокручивать её всю в память и слушать изменения, на наших объёмах там от силы 2к строк выйдет, можно по сути делать проверку revocation без отдельных походов в сеть на каждый запрос

upcFrost
() автор топика

keycloak нужен, когда ты понимаешь нах он тебе нужен. Возможно, лучше взять spring oauth2 server cконфигурить его как тебе нужно, например на хранение всего в памяти или дописать простую логику - на сохранение в db, только при рестарте или как тебе хочется. Вообще подход - берём монстра и начинаем с ним мужественно бороться, мне не очень понятен.

vtVitus ☕☕☕
()
Ответ на: комментарий от upcFrost

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

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

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

shimshimshim
()
Ответ на: комментарий от vtVitus

Вообще подход - берём монстра и начинаем с ним мужественно бороться, мне не очень понятен.

Подход скорее поменять большее зло на меньшее в адекватные сроки. Дело ж не только в oauth, там ещё куча дополнительных вещей типа яблочного логина с привязкой юзеру внешнего id, saml2 с сертификатами и т.д.

upcFrost
() автор топика
Ответ на: комментарий от shimshimshim

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

Там по крайней мере вначале будет то чтоб практически неизменно, но да, спасибо

upcFrost
() автор топика

и до 100 rps

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

с кучей статей в сети на тему что если дать фронту jwt напрямую, то будут CSRF, XSS, БТИ и другие страшные аббревиатуры.

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

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

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

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

upcFrost
() автор топика
Ответ на: комментарий от upcFrost

питон с grpc в threaded режиме напару с монгой творят чудеса

Ну, выкинуть питон с монгой и норм будет.

его надо по сути переписать с нуля

А прикрутить жвт это не с нуля?

ya-betmen 😊😊😊😊😊
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от upcFrost

питон с grpc в threaded режиме

это какие вещества надо употреблять, чтобы догадаться до питона в threaded режиме? Тем более с монгой, для которой есть асинхронные драйвера?

FishHook ☕☕☕☕☕
()
Ответ на: комментарий от FishHook

Проект переведён ещё с 2.7, что накладывает отпечаток. При этом переведён с минимальными изменениями. Чисто легаси

Тем более с монгой, для которой есть асинхронные драйвера?

Мотор? Это не асинхронный драйвер, это пачка уродливых agnostic методов, оборачивающих синхронный драйвер через run_in_executor. Я пытался написать настоящий асинхронный драйвер через Protocol (лежит на гитхабе, aiomongowire), но время и силы когда сожалению кончились. Если есть другие - скажи плз, я не нашёл

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

Собственно к чему и пришли. Дальше выбор писать свое и снова сношаться с saml2, ябблологином и прочим либо взять готовое (keycloak) и чутка обмазать кастомными дополнениями. По времени примерно один фиг

upcFrost
() автор топика

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

Да

Ну и собственно вопрос: как сделать нормально?

JWT

Или поскольку revocation в нашем случае крайне редкая штука

сделать jwt expire time в 3-5 минут. клиент после logout будет еще способен подписывать данные, если к вам ходят «клиенты» на публичный API это может стать проблемой.

Или можно сделать что-то в этом духе прямо на ингрессе?

В вашей «шине» событий, должен быть «gateway» который проверяет подпись в jwt токене и дальше «помечает» сообщение как «проверенное»

gagarin0 👍
()
Ответ на: комментарий от FishHook

Ха, не видел этого. Первый коммит 6 июня 24 года. Спасибо, ща гляну что внутри

Апд: да, похоже небо упало на землю и они родили что-то поверх BufferedProtocol, не прошло и 10 лет (а не, где-то 10 и прошло). Збс, спасибо

upcFrost
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от gagarin0

В вашей «шине» событий, должен быть «gateway» который проверяет подпись в jwt токене и дальше «помечает» сообщение как «проверенное»

Подпись проверить можно условным ngx_auth_jwt, про ингресс было скорее про revocation. Ну, в условном flask-jwt есть параметр blacklist, в который можно передать функцию проверки отзыва токена, например если все id отозванных токенов в редисе. Но в nginx такого вроде нет, хотя может плохо искал

upcFrost
() автор топика
Ответ на: комментарий от upcFrost

ngx_auth_jwt

Но в nginx такого вроде нет, хотя может плохо

Я не знаю насколько это вообще адекватно в ваших реалиях, но собрать на скорую руку можно так:

берете openresty(nginx + lua) и рядом redis

ставите https://github.com/SkyLothar/lua-resty-jwt и https://github.com/openresty/lua-resty-redis

и пишите примерно такой кодец

    server {
        default_type text/plain;
        location = /verify {
            content_by_lua '
                local cjson = require "cjson"
                local jwt = require "resty.jwt"

                local jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" ..
                    ".eyJmb28iOiJiYXIifQ" ..
                    ".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY"
                local jwt_obj = jwt:verify("lua-resty-jwt", jwt_token)

                local ok, err = red:connect("redis.openresty.com", 6379)
                local res, err = red:get(jwt_obj.id)
                if not res then
                    return 500
                end

                if res ~= ngx.null then
                    return 403
                end

                ngx.say(cjson.encode(jwt_obj))
            ';
        }
gagarin0 👍
()