Korzystanie z usług zewnętrznych z usługi Azure API Management

DOTYCZY: Wszystkie warstwy usługi API Management

Zasady dostępne w usłudze Azure API Management mogą wykonywać szeroką gamę przydatnych prac w oparciu o przychodzące żądanie, odpowiedź wychodzącą i podstawowe informacje o konfiguracji. Jednak możliwość interakcji z usługami zewnętrznymi z poziomu zasad usługi API Management otwiera o wiele więcej możliwości.

Wcześniej pokazano, jak korzystać z usługi Azure Event Hub na potrzeby rejestrowania, monitorowania i analizy. W tym artykule przedstawiono zasady, które umożliwiają interakcję z dowolną zewnętrzną usługą opartą na protokole HTTP. Te zasady mogą służyć do wyzwalania zdarzeń zdalnych lub pobierania informacji używanych do manipulowania oryginalnym żądaniem i odpowiedzią w jakiś sposób.

Wyślij jednokierunkowe żądanie

Prawdopodobnie najprostszą interakcją zewnętrzną jest styl żądania fire-and-forget, który umożliwia usłudze zewnętrznej powiadamianie o jakimś ważnym zdarzeniu. Zasady choose przepływu sterowania mogą służyć do wykrywania dowolnego rodzaju warunku, który Cię interesuje. Jeśli warunek jest spełniony, możesz wysłać zewnętrzne żądanie HTTP przy użyciu zasad send-one-way-request . Może to być żądanie do systemu obsługi komunikatów, takiego jak Hipchat lub Slack, albo interfejs API poczty, taki jak SendGrid lub MailChimp, albo w przypadku krytycznych zdarzeń pomocy technicznej, takich jak PagerDuty. Wszystkie te systemy obsługi komunikatów mają proste interfejsy API HTTP, które można wywołać.

Alerty z usługą Slack

W poniższym przykładzie pokazano, jak wysłać wiadomość do pokoju rozmów usługi Slack, jeśli kod stanu odpowiedzi HTTP jest większy lub równy 500. Błąd zakresu 500 wskazuje na problem z interfejsem API zaplecza, którego klient interfejsu API nie może rozwiązać samodzielnie. Zwykle wymaga to jakiejś interwencji w części API Management.

<choose>
  <when condition="@(context.Response.StatusCode >= 500)">
    <send-one-way-request mode="new">
      <set-url>https://hooks.slack.com/services/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</set-url>
      <set-method>POST</set-method>
      <set-body>@{
        return new JObject(
          new JProperty("username","APIM Alert"),
          new JProperty("icon_emoji", ":ghost:"),
          new JProperty("text", String.Format("{0} {1}\nHost: {2}\n{3} {4}\n User: {5}",
            context.Request.Method,
            context.Request.Url.Path + context.Request.Url.QueryString,
            context.Request.Url.Host,
            context.Response.StatusCode,
            context.Response.StatusReason,
            context.User.Email
          ))
        ).ToString();
      }</set-body>
    </send-one-way-request>
  </when>
</choose>

Slack ma pojęcie przychodzących web hooks. Podczas konfigurowania przychodzącego elementu webhook usługa Slack generuje specjalny adres URL, który umożliwia wykonywanie prostego żądania POST i przekazywanie komunikatu do kanału usługi Slack. Treść utworzonego kodu JSON jest oparta na formacie zdefiniowanym przez usługę Slack.

Slack Web Hook

Czy ogień i zapomnij wystarczająco dobry?

Istnieją pewne kompromisy podczas korzystania z stylu ognia i zapomnienia żądania. Jeśli z jakiegoś powodu żądanie zakończy się niepowodzeniem, błąd nie zostanie zgłoszony. W tej konkretnej sytuacji złożoność dodatkowego systemu raportowania błędów i dodatkowe koszty wydajności oczekiwania na odpowiedź nie są uzasadnione. W przypadku scenariuszy, w których niezbędne jest sprawdzenie odpowiedzi, zasady wysyłania żądań są lepszym rozwiązaniem.

Wyślij żądanie

Zasady send-request umożliwiają korzystanie z usługi zewnętrznej do wykonywania złożonych funkcji przetwarzania i zwracania danych do usługi API Management, która może służyć do dalszego przetwarzania zasad.

Autoryzowanie tokenów referencyjnych

Główną funkcją usługi API Management jest ochrona zasobów zaplecza. Jeśli serwer autoryzacji używany przez interfejs API tworzy tokeny JWT w ramach przepływu OAuth2, ponieważ program Microsoft Entra ID wykonuje, możesz użyć validate-jwt zasad lub validate-azure-ad-token zasad, aby zweryfikować ważność tokenu. Niektóre serwery autoryzacji tworzą tokeny referencyjne, których nie można zweryfikować bez wykonywania wywołania zwrotnego na serwerze autoryzacji.

Standaryzacja introspekcji

W przeszłości nie było ustandaryzowanego sposobu weryfikowania tokenu referencyjnego z serwerem autoryzacji. Jednak niedawno proponowany standard RFC 7662 został opublikowany przez IETF, który definiuje sposób, w jaki serwer zasobów może zweryfikować ważność tokenu.

Wyodrębnianie tokenu

Pierwszym krokiem jest wyodrębnienie tokenu z nagłówka Autoryzacja. Wartość nagłówka powinna być sformatowana przy użyciu schematu Bearer autoryzacji, pojedynczego miejsca, a następnie tokenu autoryzacji zgodnie z RFC 6750. Niestety istnieją przypadki, w których schemat autoryzacji zostanie pominięty. Aby to uwzględnić podczas analizowania, usługa API Management dzieli wartość nagłówka na spację i wybiera ostatni ciąg z zwracanej tablicy ciągów. Zapewnia to obejście dla źle sformatowanych nagłówków autoryzacji.

<set-variable name="token" value="@(context.Request.Headers.GetValueOrDefault("Authorization","scheme param").Split(' ').Last())" />

Wykonywanie żądania weryfikacji

Gdy usługa API Management ma token autoryzacji, usługa API Management może wysłać żądanie weryfikacji tokenu. RFC 7662 wywołuje tę introspekcję procesu i wymaga POST formularza HTML do zasobu introspekcji. Formularz HTML musi zawierać co najmniej parę klucz/wartość z kluczem token. To żądanie do serwera autoryzacji musi być również uwierzytelnione, aby upewnić się, że złośliwi klienci nie mogą przełęczać prawidłowych tokenów.

<send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
  <set-url>https://microsoft-apiappec990ad4c76641c6aea22f566efc5a4e.azurewebsites.net/introspection</set-url>
  <set-method>POST</set-method>
  <set-header name="Authorization" exists-action="override">
    <value>basic dXNlcm5hbWU6cGFzc3dvcmQ=</value>
  </set-header>
  <set-header name="Content-Type" exists-action="override">
    <value>application/x-www-form-urlencoded</value>
  </set-header>
  <set-body>@($"token={(string)context.Variables["token"]}")</set-body>
</send-request>

Sprawdzanie odpowiedzi

Atrybut response-variable-name jest używany do udzielenia dostępu zwróconej odpowiedzi. Nazwa zdefiniowana w tej właściwości może służyć jako klucz do słownika IResponse w context.Variables celu uzyskania dostępu do obiektu.

Z obiektu odpowiedzi można pobrać treść i RFC 7622 informuje usługę API Management, że odpowiedź musi być obiektem JSON i musi zawierać co najmniej właściwość o nazwie active , która jest wartością logiczną. Jeśli active wartość ma wartość true, token jest uznawany za prawidłowy.

Alternatywnie, jeśli serwer autoryzacji nie zawiera pola "aktywne", aby wskazać, czy token jest prawidłowy, użyj narzędzia takiego jak Postman, aby określić, jakie właściwości są ustawione w prawidłowym tokenie. Jeśli na przykład prawidłowa odpowiedź tokenu zawiera właściwość o nazwie "expires_in", sprawdź, czy ta nazwa właściwości istnieje w odpowiedzi serwera autoryzacji w następujący sposób:

<when condition="@(((IResponse)context.Variables["tokenstate"]).Body.As<JObject>().Property("expires_in") == null)">

Raportowanie błędu

Możesz użyć <choose> zasad, aby wykryć, czy token jest nieprawidłowy, a jeśli tak, zwraca odpowiedź 401.

<choose>
  <when condition="@((bool)((IResponse)context.Variables["tokenstate"]).Body.As<JObject>()["active"] == false)">
    <return-response response-variable-name="existing response variable">
      <set-status code="401" reason="Unauthorized" />
      <set-header name="WWW-Authenticate" exists-action="override">
        <value>Bearer error="invalid_token"</value>
      </set-header>
    </return-response>
  </when>
</choose>

Zgodnie z RFC 6750 , który opisuje sposób bearer użycia tokenów, usługa API Management zwraca również nagłówek z odpowiedzią WWW-Authenticate 401. Usługa WWW-Authenticate ma na celu poinstruowanie klienta o sposobie konstruowania prawidłowo autoryzowanego żądania. Ze względu na szeroką gamę metod możliwych w strukturze OAuth2 trudno jest przekazać wszystkie potrzebne informacje. Na szczęście trwają prace, aby pomóc klientom odkryć, jak prawidłowo autoryzować żądania do serwera zasobów.

Ostateczne rozwiązanie

Na końcu uzyskasz następujące zasady:

<inbound>
  <!-- Extract Token from Authorization header parameter -->
  <set-variable name="token" value="@(context.Request.Headers.GetValueOrDefault("Authorization","scheme param").Split(' ').Last())" />

  <!-- Send request to Token Server to validate token (see RFC 7662) -->
  <send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
    <set-url>https://microsoft-apiappec990ad4c76641c6aea22f566efc5a4e.azurewebsites.net/introspection</set-url>
    <set-method>POST</set-method>
    <set-header name="Authorization" exists-action="override">
      <value>basic dXNlcm5hbWU6cGFzc3dvcmQ=</value>
    </set-header>
    <set-header name="Content-Type" exists-action="override">
      <value>application/x-www-form-urlencoded</value>
    </set-header>
    <set-body>@($"token={(string)context.Variables["token"]}")</set-body>
  </send-request>

  <choose>
    <!-- Check active property in response -->
    <when condition="@((bool)((IResponse)context.Variables["tokenstate"]).Body.As<JObject>()["active"] == false)">
      <!-- Return 401 Unauthorized with http-problem payload -->
      <return-response response-variable-name="existing response variable">
        <set-status code="401" reason="Unauthorized" />
        <set-header name="WWW-Authenticate" exists-action="override">
          <value>Bearer error="invalid_token"</value>
        </set-header>
      </return-response>
    </when>
  </choose>
  <base />
</inbound>

Jest to tylko jeden z wielu przykładów sposobu, w jaki send-request zasady mogą służyć do integrowania przydatnych usług zewnętrznych z procesem żądań i odpowiedzi przepływających za pośrednictwem usługi API Management.

Kompozycja odpowiedzi

Zasady send-request mogą służyć do ulepszania podstawowego żądania do systemu zaplecza, jak pokazano w poprzednim przykładzie, lub mogą być używane jako kompletna zamiana wywołania zaplecza. Przy użyciu tej techniki można łatwo tworzyć zasoby złożone, które są agregowane z wielu różnych systemów.

Tworzenie pulpitu nawigacyjnego

Czasami chcesz mieć możliwość uwidocznienia informacji, które istnieją w wielu systemach zaplecza, na przykład w celu obsługi pulpitu nawigacyjnego. Kluczowe wskaźniki wydajności pochodzą ze wszystkich różnych zapleczy, ale wolisz nie zapewnić bezpośredniego dostępu do nich i byłoby miło, gdyby wszystkie informacje mogły zostać pobrane w jednym żądaniu. Być może niektóre informacje zaplecza wymagają wycięć i podyktowania i trochę odczyszać najpierw! Możliwość buforowania tego zasobu złożonego może być przydatna do zmniejszenia obciążenia zaplecza, ponieważ wiesz, że użytkownicy mają zwyczaj wbijania klawisza F5 w celu sprawdzenia, czy ich metryki nienadzorujące mogą ulec zmianie.

Faking the resource (Faking the resource)

Pierwszym krokiem do utworzenia zasobu pulpitu nawigacyjnego jest skonfigurowanie nowej operacji w witrynie Azure Portal. Jest to operacja zastępcza używana do konfigurowania zasad kompozycji w celu utworzenia zasobu dynamicznego.

Operacja pulpitu nawigacyjnego

Wykonywanie żądań

Po utworzeniu operacji można skonfigurować zasady specjalnie dla tej operacji.

Zrzut ekranu przedstawiający ekran Zakres zasad.

Pierwszym krokiem jest wyodrębnienie wszystkich parametrów zapytania z żądania przychodzącego, aby można było przekazać je do zaplecza. W tym przykładzie pulpit nawigacyjny wyświetla informacje na podstawie okresu i dlatego ma fromDate parametr i toDate . Możesz użyć set-variable zasad, aby wyodrębnić informacje z adresu URL żądania.

<set-variable name="fromDate" value="@(context.Request.Url.Query["fromDate"].Last())">
<set-variable name="toDate" value="@(context.Request.Url.Query["toDate"].Last())">

Po uzyskaniu tych informacji możesz wysyłać żądania do wszystkich systemów zaplecza. Każde żądanie tworzy nowy adres URL z informacjami o parametrach i wywołuje odpowiedni serwer i przechowuje odpowiedź w zmiennej kontekstowej.

<send-request mode="new" response-variable-name="revenuedata" timeout="20" ignore-error="true">
  <set-url>@($"https://accounting.acme.com/salesdata?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")</set-url>
  <set-method>GET</set-method>
</send-request>

<send-request mode="new" response-variable-name="materialdata" timeout="20" ignore-error="true">
  <set-url>@($"https://inventory.acme.com/materiallevels?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")</set-url>
  <set-method>GET</set-method>
</send-request>

<send-request mode="new" response-variable-name="throughputdata" timeout="20" ignore-error="true">
  <set-url>@($"https://production.acme.com/throughput?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")</set-url>
  <set-method>GET</set-method>
</send-request>

<send-request mode="new" response-variable-name="accidentdata" timeout="20" ignore-error="true">
  <set-url>@($"https://production.acme.com/accidentdata?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")</set-url>
  <set-method>GET</set-method>
</send-request>

Usługa API Management będzie wysyłać te żądania sekwencyjnie.

Odpowiadać

Aby utworzyć złożoną odpowiedź, można użyć zasad return-response . Element set-body może użyć wyrażenia, aby skonstruować nowy JObject element ze wszystkimi reprezentacjami składników osadzonymi jako właściwości.

<return-response response-variable-name="existing response variable">
  <set-status code="200" reason="OK" />
  <set-header name="Content-Type" exists-action="override">
    <value>application/json</value>
  </set-header>
  <set-body>
    @(new JObject(new JProperty("revenuedata",((IResponse)context.Variables["revenuedata"]).Body.As<JObject>()),
                  new JProperty("materialdata",((IResponse)context.Variables["materialdata"]).Body.As<JObject>()),
                  new JProperty("throughputdata",((IResponse)context.Variables["throughputdata"]).Body.As<JObject>()),
                  new JProperty("accidentdata",((IResponse)context.Variables["accidentdata"]).Body.As<JObject>())
                  ).ToString())
  </set-body>
</return-response>

Pełne zasady wyglądają następująco:

<policies>
  <inbound>
    <set-variable name="fromDate" value="@(context.Request.Url.Query["fromDate"].Last())">
    <set-variable name="toDate" value="@(context.Request.Url.Query["toDate"].Last())">

    <send-request mode="new" response-variable-name="revenuedata" timeout="20" ignore-error="true">
      <set-url>@($"https://accounting.acme.com/salesdata?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")"</set-url>
      <set-method>GET</set-method>
    </send-request>

    <send-request mode="new" response-variable-name="materialdata" timeout="20" ignore-error="true">
      <set-url>@($"https://inventory.acme.com/materiallevels?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")"</set-url>
      <set-method>GET</set-method>
    </send-request>

    <send-request mode="new" response-variable-name="throughputdata" timeout="20" ignore-error="true">
      <set-url>@($"https://production.acme.com/throughput?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")"</set-url>
      <set-method>GET</set-method>
    </send-request>

    <send-request mode="new" response-variable-name="accidentdata" timeout="20" ignore-error="true">
      <set-url>@($"https://production.acme.com/accidentdata?from={(string)context.Variables["fromDate"]}&to={(string)context.Variables["fromDate"]}")"</set-url>
      <set-method>GET</set-method>
    </send-request>

    <return-response response-variable-name="existing response variable">
      <set-status code="200" reason="OK" />
      <set-header name="Content-Type" exists-action="override">
        <value>application/json</value>
      </set-header>
      <set-body>
        @(new JObject(new JProperty("revenuedata",((IResponse)context.Variables["revenuedata"]).Body.As<JObject>()),
                      new JProperty("materialdata",((IResponse)context.Variables["materialdata"]).Body.As<JObject>()),
                      new JProperty("throughputdata",((IResponse)context.Variables["throughputdata"]).Body.As<JObject>()),
                      new JProperty("accidentdata",((IResponse)context.Variables["accidentdata"]).Body.As<JObject>())
        ).ToString())
      </set-body>
    </return-response>
  </inbound>
  <backend>
    <base />
  </backend>
  <outbound>
    <base />
  </outbound>
</policies>

Podsumowanie

Usługa Azure API Management udostępnia elastyczne zasady, które można selektywnie stosować do ruchu HTTP i umożliwiają tworzenie usług zaplecza. Niezależnie od tego, czy chcesz ulepszyć bramę interfejsu API za pomocą funkcji alertów, weryfikacji, możliwości weryfikacji, czy tworzyć nowe zasoby złożone na podstawie wielu usług zaplecza, send-request powiązane zasady otwierają świat możliwości.