Menggunakan layanan eksternal dari layanan Azure API Management

BERLAKU UNTUK: Semua tingkatAN API Management

Kebijakan yang tersedia dalam layanan Azure API Management dapat melakukan berbagai pekerjaan yang berguna berdasarkan murni pada permintaan masuk, respons keluar, dan informasi konfigurasi dasar. Namun, dapat berinteraksi dengan layanan eksternal dari kebijakan API Management membuka lebih banyak peluang.

Anda sebelumnya telah melihat cara berinteraksi dengan layanan Azure Event Hub untuk pembuatan log, pemantauan, dan analitik. Artikel ini menunjukkan kebijakan yang memungkinkan Anda berinteraksi dengan layanan berbasis HTTP eksternal apa pun. Kebijakan ini dapat digunakan untuk memicu kejadian jarak jauh atau untuk mengambil informasi yang digunakan untuk memanipulasi permintaan dan respons asli dalam beberapa cara.

Kirim-Permintaan-Satu-Arah

Mungkin interaksi eksternal yang paling sederhana adalah gaya permintaan lepaskan-dan-lupakan yang memungkinkan layanan eksternal untuk diberitahu tentang beberapa jenis peristiwa penting. Kebijakan alur kontrol choose dapat digunakan untuk mendeteksi segala jenis kondisi yang Anda minati. Jika kondisinya terpenuhi, Anda dapat membuat permintaan HTTP eksternal menggunakan kebijakan kirim-permintaan-satu-arah. Ini bisa berupa permintaan ke sistem perpesanan seperti Hipchat atau Slack, atau API email seperti SendGrid atau MailChimp, atau untuk insiden dukungan kritis seperti PagerDuty. Semua sistem perpesanan ini memiliki API HTTP sederhana yang dapat dipanggil.

Pemberitahuan dengan Slack

Contoh berikut menunjukkan cara mengirim pesan ke ruang obrolan Slack jika kode status respons HTTP lebih besar dari atau sama dengan 500. Kesalahan rentang 500 menunjukkan masalah dengan API backend yang tidak dapat diselesaikan oleh klien API sendiri. Biasanya memerlukan semacam intervensi pada bagian 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 memiliki gagasan tentang webhook masuk. Saat mengonfigurasi webhook masuk, Slack menghasilkan URL khusus, yang memungkinkan Anda untuk melakukan permintaan POST sederhana dan meneruskan pesan ke saluran Slack. Bodi JSON yang Anda buat didasarkan pada format yang ditentukan oleh Slack.

Webhook Slack

Apakah lepaskan dan lupakan cukup baik?

Ada pengorbanan tertentu saat menggunakan gaya permintaan lepaskan-dan-lupakan. Jika karena alasan tertentu, permintaan gagal, maka kegagalan tidak akan dilaporkan. Dalam situasi khusus ini, kompleksitas dari memiliki sistem pelaporan kegagalan sekunder dan biaya kinerja tambahan untuk menunggu respons tidak dijamin. Untuk skenario di mana penting untuk memeriksa respons, maka kebijakan kirim-permintaan adalah pilihan yang lebih baik.

Kirim-Permintaan

Kebijakan send-request ini memungkinkan penggunaan layanan eksternal untuk melakukan fungsi pemrosesan yang kompleks dan mengembalikan data ke layanan manajemen API yang dapat digunakan untuk pemrosesan kebijakan lebih lanjut.

Mengotorisasi token referensi

Fungsi utama dari API Management adalah melindungi sumber daya backend. Jika server otorisasi yang digunakan oleh API Anda membuat token JWT sebagai bagian dari alur OAuth2-nya, seperti yang dilakukan Microsoft Entra ID , maka Anda dapat menggunakan validate-jwt kebijakan atau validate-azure-ad-token kebijakan untuk memverifikasi validitas token. Beberapa server otorisasi membuat apa yang disebut token referensi yang tidak dapat diverifikasi tanpa melakukan panggilan balik ke server otorisasi.

Introspeksi terstandardisasi

Di masa lalu, belum ada cara terstandardisasi untuk memverifikasi token referensi dengan server otorisasi. Namun RFC 7662 standar yang baru-baru ini diusulkan telah diterbitkan oleh IETF yang menentukan bagaimana server sumber daya dapat memverifikasi validitas token.

Mengekstrak token

Langkah pertama adalah mengekstrak token dari header Otorisasi. Nilai header harus diformat dengan skema otorisasi Bearer, spasi tunggal, lalu token otorisasi sesuai RFC 6750. Sayangnya ada kasus di mana skema otorisasi dihilangkan. Untuk memperhitungkan hal ini saat mengurai, API Management membagi nilai header pada spasi dan memilih string terakhir dari array string yang dikembalikan. Ini menyediakan solusi untuk header otorisasi yang diformat dengan buruk.

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

Membuat permintaan validasi

Setelah API Management memiliki token otorisasi, API Management dapat membuat permintaan untuk memvalidasi token. RFC 7662 menyebut proses ini introspeksi dan mengharuskan Anda POST formulir HTML ke sumber daya introspeksi. Formulir HTML setidaknya harus berisi pasangan kunci/nilai dengan token kunci. Permintaan ini ke server otorisasi juga harus diautentikasi, untuk memastikan bahwa klien jahat tidak dapat menelusuri token yang valid.

<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>

Memeriksa respons

Atribut response-variable-name ini digunakan untuk memberikan akses respons yang dikembalikan. Nama yang didefinisikan dalam properti ini dapat digunakan sebagai kunci ke dalam kamus context.Variables untuk mengakses objek IResponse.

Dari objek respons, Anda dapat mengambil bodi dan RFC 7622 memberi tahu API Management bahwa respons harus merupakan objek JSON dan harus berisi setidaknya properti yang disebut active yang merupakan nilai boolean. Ketika active benar maka token dianggap valid.

Atau, jika server otorisasi tidak menyertakan bidang "aktif" untuk menunjukkan apakah token valid, gunakan alat seperti Postman untuk menentukan properti apa yang diatur dalam token yang valid. Misalnya, jika respons token yang valid berisi properti yang disebut "expires_in", periksa apakah nama properti ini ada dalam respons server otorisasi dengan cara ini:

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

Kegagalan pelaporan

Anda dapat menggunakan kebijakan <choose> untuk mendeteksi apakah token tidak valid dan jika demikian, mengembalikan respons 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>

Sesuai RFC 6750 yang menjelaskan bagaimana token bearer harus digunakan, API Management juga mengembalikan header WWW-Authenticate dengan respons 401. Laporan WWW-Authenticate dimaksudkan untuk menginstruksikan klien tentang cara membangun permintaan yang diotorisasi dengan benar. Karena berbagai pendekatan yang mungkin dilakukan dengan kerangka kerja OAuth2, sulit untuk mengkomunikasikan semua informasi yang diperlukan. Untungnya ada upaya yang dilakukan untuk membantu klien menemukan cara mengotorisasi permintaan dengan benar ke server sumber daya.

Solusi akhir

Pada akhirnya, Anda mendapatkan kebijakan berikut:

<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>

Ini hanya salah satu dari banyak contoh bagaimana kebijakan send-request dapat digunakan untuk mengintegrasikan layanan eksternal yang berguna ke dalam proses permintaan dan respons yang mengalir melalui layanan API Management.

Komposisi Respons

Kebijakan send-request ini dapat digunakan untuk meningkatkan permintaan utama ke sistem backend, seperti yang Anda lihat dalam contoh sebelumnya, atau dapat digunakan sebagai penggantian lengkap untuk panggilan backend. Dengan menggunakan teknik ini Anda dapat dengan mudah membuat sumber daya komposit yang dikumpulkan dari beberapa sistem yang berbeda.

Membuat dasbor

Terkadang Anda ingin dapat mengekspos informasi yang ada di beberapa sistem backend, misalnya, untuk menggerakkan dasbor. KPI berasal dari semua backend yang berbeda, tetapi Anda lebih suka tidak memberikan akses langsung kepada mereka dan alangkah baiknya jika semua informasi dapat diambil dalam satu permintaan. Mungkin beberapa informasi backend perlu diiris dan dikotakkan dan sedikit sanitasi terlebih dahulu! Mampunya menyimpan sumber daya komposit itu akan berguna untuk mengurangi beban backend seperti yang Anda tahu pengguna memiliki kebiasaan mengetuk ngetuk tombol F5 untuk melihat apakah metrik mereka yang berkinerja buruk dapat berubah.

Memalsukan sumber daya

Langkah pertama untuk membangun sumber daya dasbor adalah mengonfigurasi operasi baru di portal Microsoft Azure. Ini adalah operasi placeholder yang digunakan untuk mengonfigurasi kebijakan komposisi untuk membangun sumber daya dinamis.

Operasi dasbor

Membuat permintaan

Setelah operasi dibuat, Anda dapat mengonfigurasi kebijakan khusus untuk operasi tersebut.

Cuplikan layar yang memperlihatkan layar lingkup Kebijakan.

Langkah pertama adalah mengekstrak parameter kueri apa pun dari permintaan masuk, sehingga Anda bisa meneruskannya ke backend. Dalam contoh ini, dasbor menampilkan informasi berdasarkan periode waktu dan karenanya memiliki parameter fromDate dan toDate. Anda dapat menggunakan kebijakan set-variable untuk mengekstrak informasi dari URL permintaan.

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

Setelah Anda memiliki informasi ini, Anda dapat membuat permintaan ke semua sistem backend. Setiap permintaan membuat URL baru dengan informasi parameter dan memanggil server masing-masingnya dan menyimpan respons dalam variabel konteks.

<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>

API Management akan mengirim permintaan ini secara berurutan.

Merespons

Untuk membangun respons komposit, Anda dapat menggunakan kebijakan pengembalian-respons. Elemen set-body dapat menggunakan ekspresi untuk membuat JObject baru dengan semua representasi komponen yang disematkan sebagai properti.

<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>

Kebijakan lengkap terlihat sebagai berikut:

<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>

Ringkasan

Layanan Azure API Management menyediakan kebijakan fleksibel yang dapat diterapkan secara selektif pada lalu lintas HTTP dan memungkinkan komposisi layanan backend. Apakah Anda ingin menyempurnakan gateway API Anda dengan fungsi pemberitahuan, verifikasi, kemampuan validasi, atau membuat sumber daya komposit baru berdasarkan beberapa layanan backend, send-request dan kebijakan terkait membuka banyak sekali kemungkinan.