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)
Ответ на: комментарий от 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
()