Comments 17
Спасибо за статью. После прочтения возникает пару вопросов.
Если у нас на гетевей ломится фронт, к примеру реактовский, то как он определит, что сессии нет и надо показать страницу логина? В данном решении, как понимаю, в ответ фронт получит html со страницей аутентификации и как то должен её переварить? Или он всё таки должен в ответ получить 401ю без контента и тогда перенаправлять на страницу логина(свою реактовскую). Я встречал подобные решения типа сберовского IAM Proxy, который запрос из браузера на основании id сессии отправлял либо на внутреннюю страницу аутентификации(если сесиии нет), либо, в соответствии с настроенной маршрутизацией, на сервисы с фронтом (SPA) или гетевей. При этом все запросы обогащались токенами.
Есть ли какое нибудь кэширование или каждый запрос на гетевей по http вызывает обращение в редис за сессией?
Если у нас на гетевей ломится фронт, к примеру реактовский, то как он определит, что сессии нет и надо показать страницу логина?
Это поймет сам шлюз и фронт получит редирект на страницу авторизации сервера авторизации. Даже если сессия до этого была и были просто почищены куки. При этом старая сессия будет висеть пока не отвалится сама.
Есть ли какое нибудь кэширование или каждый запрос на гетевей по http вызывает обращение в редис за сессией?
В общем-то, сам редис частично выполняет функцию кэша, в памяти авторизационные данные не кэшируются. Это могло бы быть актуально для небольшого числа сессий, но представьте если у вас, например, 2 млн живых пользователей, и вам нужно держать в памяти 2 млн активных сессий, т.е. объектов OAuth2AuthorizedClient.
Лучше так не делать.
То есть получается, что браузер тянет SPA тоже через гетевей? Если же это не так, и первый запрос гетевею идет из браузера с уже загруженным SPA, то редирект придется прописывать вручную в коде фронта, т.к. запрос к апи, который вернул 302, скорее всего не приведет к уходу на страницу аутентификации(не уверен на 100%, надо проверять).
По кешированию - имел дело с решением, где мы на гетевее парсим токен и проверяем его подпись открытым ключом, который как раз закеширован. В том решении сессий нет, токен выдали и доверяем ему, пока не протухнет. В описанном примере понятно, что без редиса не обойтись, просто было интересно есть ли еще дополнительные кэши - видимо нет.
А где кейклок держит информацию о сессии? Гетевей хранит в редисе, кейклок скорее всего в своей бд, выходит дублирование?
Правильно ли я понимаю, что фронт логиниться один раз, при этом создаётся сессия на какой-то срок, и фронт следующие запросы отправляет без токена?
Все верно. В следующий раз для доступа к авторизованной сессии фронт будет использовать куку. Правильнее сказать браузер будет использовать куку.
Очень полезная статья, спасибо!
Правда по безопасности такой вариант не всегда прокатит((( могут придраться
Не волнуйтесь, по безопасности такой вариант на данный момент - лучшее, что можно сделать:
На шлюзе есть csrf токены от межсайтового скриптинга.
Аксес и рефреш токены не хранятся на стороне клиента.
Запрос не доходит до бэка если есть проблемы с авторизацией, а остается на шлюзе.
В целом, такая архитектура это наиболее безопасный и оптимальный вариант.
По статье понял, что фронт обращается за токеном на бэк, а он обращается в кейклоак. А почему бы не обращаться с фронта в кейклок для получения токена, а бэк только валидирует этот токен? И еще не понятен мне момент: только шлюз проверяет токен? А как запретить тогда запросы к микросервисам отовсюду, кроме шлюза?
А почему бы не обращаться с фронта в кейклок для получения токена, а бэк только валидирует этот токен?
Потому что в таком случае фронт должен быть публичным клиентом и токены будут храниться локально в браузере. А это открывает некоторые векторы атак. Плюс реализация флоу авторизации на бэке гораздо более простая и надежная, чем на фронте.
И еще не понятен мне момент: только шлюз проверяет токен? А как запретить тогда запросы к микросервисам отовсюду, кроме шлюза?
Бэк настроен как ресурс-сервер, т.е. валидация токена и проверка ролей происходит на бэке. Задача шлюза - обеспечить процесс авторизации по флоу, т.е. получения токена, и удержание его в сессии. Валидацию токена шлюз, естественно, выполняет, но проверку ролей. То есть шлюз может выдать 401, не дав заведомо невалидному токену дойти до бэка, а бэк может выдать 403, если у токена недостаточно прав для доступа к ресурсу.
Для ограничения доступа к шлюзу с других клиентов вам нужно сделать свой клиентский скоуп и контролировать его на стороне шлюза. Я не написал про это в этой статье, но про это можно почитать тут.
Спасибо за статью, но поправьте, пожалуйста, опечатки типа "В spring security для этого есть эндпоит /logaut
"
Вопрос, почему BFF на GW, а не в отдельном сервисе? Ведь шлюз для маршрутизации, a BFF, для авторизации.
В application.yaml у вас указан client-secret. Что это за пароль, учитывая, что во время аутентификации вы вводите логин/пароль?
Спасибо за статью, но поправьте, пожалуйста, опечатки типа "В spring security для этого есть эндпоит
/logaut
"
Спасибо, исправил.
Вопрос, почему BFF на GW, а не в отдельном сервисе? Ведь шлюз для маршрутизации, a BFF, для авторизации.
TokenRelay реализован на шлюзе в виде фильтра. Если использовать еще один bff, то придется уже на нем реализовывать TokenRelay или нечто подобное. К тому же архитектура, в которой bff это и есть шлюз, явно проще.
В application.yaml у вас указан client-secret. Что это за пароль, учитывая, что во время аутентификации вы вводите логин/пароль?
Это секрет oauth 2.0 клиента. Клиенты бывают двух видов - публичные и приватные. У приватных есть секрет, и они считаются более безопасными.
Подскажите еще такое, пожалуйста.
Вы используете @EnableWebFluxSecurity , но Redis используете не реактивный. Получается, сессия всё равно частично хранится внутри SPring. В чем тогда смысл использования Redis в вашем примере?
Вы используете
@EnableWebFluxSecurity , но Redis используете не реактивный
Что значит "редис используется не реактивный"? Реактивным может быть коннект к редису и, как следствие, весь код, так или иначе связанный с ним. И здесь он самый что ни на есть реактивный - смотрите на аннотацию @EnableRedisWebSession,это добавляет поддержку реактивных сессий в Redis.
Получается, сессия всё равно частично хранится внутри SPring
Что значит хранятся внутри "SPring"? Что такое в вашем понимании "SPring"? В данном примере персистентность сессий полностью реализована в редисе и только в нем.
Spring Cloud Gateway + Keycloak: полноценный пример