Я реализовал архитектуру Backend for Frontend (BFF) с помощью Spring Webflux, используя фильтр TokenRelay. Мой интерфейс взаимодействует с BFF с помощью файла cookie в качестве механизма аутентификации, а BFF получает токен JWT от Cognito (или сохраняется в сеансе Spring, не уверен. Для меня это волшебство) и отправляет его в заголовке на серверы ресурсов. Здесь узел BFF работает как конфиденциальный клиент oauth2, и браузеру не нужно хранить какой-либо токен JWT или что-то подобное.
Это делается прозрачно для меня как разработчика. Он настроен правильно и работает без проблем. Если я делаю запрос к https://bff-url/api/users/{user-id} из внешнего интерфейса, он работает, автоматически перенаправляясь на микросервис пользователей и прикрепляя токен в заголовок.
Однако давайте рассмотрим конечную точку, которой нет ни на одном ресурсном сервере (микросервисе). Эта конечная точка находится в BFF.
Этот URL-адрес конечной точки — https://bff-url/api/auth/me. Я определил следующим образом:
@RestController
@RequestMapping("/api/auth")
public class CurrentUserController {
public CurrentUserController() {
}
@GetMapping("/me")
@ResponseBody
public Mono<UserResponseDTO> getCurrentUser(Authentication authentication) {
String userId = ((DefaultOidcUser)authentication.getPrincipal()).getClaim("custom:userId");
// TODO call to users service users with this userId
return Mono.empty();
}
}
Я просто хочу получить идентификатор из сеанса (я его получаю) и вызвать службу пользователей, как обычно. Я не знаю, какой подход лучше всего сделать:
Я открыт для любого другого подхода в соответствии с рекомендациями Spring, поскольку я не эксперт Spring и, вероятно, что-то упускаю.
Как получить токен JWT в BFF
Самый простой способ — использовать фильтр TokenRelay
, чтобы Spring Cloud Gateway прикрепил его в качестве заголовка авторизации носителя к нижестоящему серверу ресурсов.
BFF получает токен JWT от Cognito (или сохраняется в сеансе Spring, не уверен. Для меня это волшебство)
И то, и другое: BFF — это клиент OAuth2, настроенный на использование потока кода авторизации (oauth2Login
в формулировке Spring Security) => именно так он изначально получает токены с сервера авторизации (дальнейшие токены могут быть получены с использованием потока токена обновления). Затем он сохраняет эти токены в сеансах. Таким образом, для дальнейших запросов к BFF токены считываются в сеансе (если только срок его действия не истек или наступит в течение следующей минуты).
Я открыт для любого другого подхода в соответствии с рекомендациями Spring, поскольку я не эксперт Spring и, вероятно, что-то упускаю.
Самый простой способ — использовать BFF исключительно для целей маршрутизации (адаптация файлов cookie сеанса / авторизации носителя на лету) и разместить его /me
на нижестоящем сервере ресурсов. Поскольку TokenRelay
прикрепляет токен доступа для текущего пользователя, все, что необходимо на нижестоящем сервере ресурсов, — это отразить утверждения токена доступа (все это или, возможно, лучше, только необходимые). Это то, что я делаю в этой статье Baeldung, посвященной шаблону OAuth2 BFF , в другом проекте и вообще во многих других местах.
Если вы предпочитаете использовать конечную точку /me
на BFF, есть два варианта (но ни один из них не является таким простым и масштабируемым, как приведенный выше):
ReactiveOAuth2AuthorizedClientManager
для авторизации запроса к пользовательскому сервису, а затем проанализировать ответ для создания UserDto (но тогда почему бы не использовать шлюзовой маршрут к пользовательскому сервису с фильтром TokenRelay
?)Я думаю, что будет чище держать бизнес-логику подальше от шлюза. Единственное исключение, которое я делаю, — это доступные параметры входа в систему, поскольку в BFF определяются конфигурации OAuth2 provider(s)
и registration(s)
. Эта конечная точка информации о владельце ресурса лучше подходит для нижестоящего сервера ресурсов, к которому, естественно, уже прикреплен токен доступа. Мои «пользовательские сервисы» обычно представляют собой адаптер к API сервера авторизации, и мне кажется удобным поместить на него конечную точку, отражающую владельца ресурса, но вы можете прекрасно создать выделенные микросервисы, если считаете, что это чище.
Огромное спасибо @ch4mp за этот очень подробный ответ. Ты ведешь меня к успеху! В конце концов я решил получить токен доступа от AuthorizedClientManager, так как хочу отделить его от службы пользователей. Фактически, мне не нужен токен, поскольку Spring сделал ретрансляцию токена прозрачной при настройке oauth2Client в компоненте WebClient. Спасибо!