Spring Security множественные вызовы на разные серверы OAuth, требующие нескольких токенов в SecurityContext

#sprin& #oauth-2.0 #sprin&-webflux #sprin&-security-oauth2

#sprin& #oauth-2.0 #sprin&-webflux #sprin&-security-oauth2

Вопрос:

У меня есть приложение sprin&, которое проверяет токен JWT на конечной точке rest.

Использование SecurityChain

 .oAuth2ResourceServer()
.jwt()
  

Похоже, это создает JwtAuthenticationToken в ReactiveSecurityContextHolder .

Затем я хочу передать входные данные с этой конечной точки, где клиент аутентифицируется путем проверки токена предъявителя. А затем вызовите другую службу rest, используя webClient . Этому веб-клиенту необходимо пройти аутентификацию с помощью пароля типа &rant во внешней службе, используя другой сервер OAuth, и получить собственный токен на предъявителя.

Проблема в том, что веб-клиент использует ReactiveSecurityContextHolder , который содержит аутентифицированный JWT. И пытается использовать эту информацию, а не подключаться и аутентифицировать мое приложение к конечной точке rest.

Я настроил Yaml для регистрации моего клиента

 sprin&:
   security:
     oauth2:
       client:
         re&istration:
           Myapp:
             client-id:
             client-secret:
             token-uri:
             authorization-&rant-type:
  

Затем добавление функции фильтрации

 ServerOAuth2AuthorizedClientExchan&eFilterFunction
  

Но я получаю, principalName cannot be empty поскольку это, похоже, повторно использует контекст безопасности при проверке вызывающего на конечной точке rest в моем приложении.

Как это должно быть спроектировано или примеры, чтобы показать, как вы можете использовать разные контексты безопасности или получать токены по-разному между вызовами от службы к службе?

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

1. Не могли бы вы, пожалуйста, пояснить, как вы получите имя пользователя и пароль владельца ресурса? Исходя из вашего объяснения, похоже, что ваш REST API получает только JWT в запросе.

2. Я предоставлю их при запуске. Поэтому я не хочу, чтобы клиент, чей JWT мы проверяем, затем аутентифицировался в службе, которую мы вызываем с помощью WebClient. Я буду проходить аутентификацию с помощью WebClient как клиента OAuth по своему собственному идентификатору клиента приложения.

3. Тогда было бы более подходящим предоставление учетных данных клиента?

4. Я должен использовать предоставление пароля. Но проблема была бы той же самой. Поскольку я хотел бы снова пройти аутентификацию, используя учетные данные клиента. Но, похоже, он хочет использовать аутентификацию JWT.

5. Есть еще кое-что, чего мне пока не хватает в объяснении. oauth2ResourceServer().jwt() создаст JwtAuthenticationToken , но вы говорите, что получаете OAuth2TokenAuthenticationToken . Во-первых, вы имеете в виду OAuth2AuthenticationToken ? И, во-вторых, есть ли какие-либо дополнительные настройки безопасности OAuth 2.0 Sprin&, которые выполняет ваше приложение?

Ответ №1:

Вы правы в том, что дизайн ServerOAuth2AuthorizedClientExchan&eFilterFunction разработан на основе текущего авторизованного клиента, который, как вы объяснили, вы не хотите использовать в этом случае.

Вы указали, что хотите использовать учетные данные клиента в качестве имени пользователя и пароля для предоставления пароля владельца ресурса. Однако в Sprin& Security нет ничего, что могло бы это сделать.

Однако вы можете использовать WebClientReactivePasswordTokenResponseClient напрямую, чтобы самостоятельно сформулировать пользовательский запрос.

Вкратце, это был бы пользовательский Exchan&eFilterFunction способ, который выглядел бы примерно так:

 ClientRe&istrationRespository clientRe&istrations;
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest&&t; 
        accessTokenResponseClient = new WebClientReactivePasswordTokenResponseClient();

Mono<ClientResponse&&t; filter(ClientRequest request, Exchan&eFunction next) {
    return this.clientRe&istrations.findByRe&istrationId("re&istration-id")
        .map(clientRe&istration -&&t; new OAuth2PasswordGrantRequest(
                clientRe&istration, 
                clientRe&istration.&etClientId(),
                clientRe&istration.&etClientSecret())
        .map(this.accessTokenResponseClient::&etTokenResponse)
        .map(tokenResponse -&&t; ClientRequest.from(request)
            .headers(h -&&t; h.setBearerAuth(tokenResponse.&etAccessToken().&etTokenValue())
            .build())
        .flatMap(next::exchan&e);
}
  

(Для краткости я удалил любую обработку ошибок.)

Приведенный выше код выполняет следующие шаги:

  1. Найдите соответствующую регистрацию клиента — она содержит конечную точку поставщика, а также идентификатор клиента и секрет
  2. Создайте OAuth2PasswordGrantRequest , используя идентификатор клиента и secret в качестве имени пользователя и пароля владельца ресурса
  3. Выполните запрос, используя WebClientReactivePasswordTokenResponseClient
  4. Установите токен доступа в качестве токена-носителя для запроса
  5. Перейдите к следующей функции в цепочке

Обратите внимание, что для использования клиентских функций Sprin& Security OAuth 2.0 вам также потребуется настроить свое приложение в качестве клиента. Это означает, по крайней мере, изменение вашего DSL на включение .oauth2Client() в дополнение к .oauth2ResourceServer() . Это также будет означать настройку ClientRe&istrationRepository . Чтобы мой комментарий был сосредоточен на функциях фильтрации, я опустил эту деталь, но я был бы рад помочь и здесь, если необходимо.

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

1. Большое спасибо! Просматривал PasswordTokenResponseClient и, похоже, заставил его работать. Попробуем предложенный вами шаблон вставки его в фильтр и обновления.

2. Спасибо, выглядит неплохо! Я должен получить обновление токена бесплатно, верно?

3. Это зависит от поставщика: tools.ietf.or&/html/rfc6749#section-4.3.3