適用対象: すべての API Management レベル
Azure API Management サービスに含まれるポリシーでは、着信要求、送信応答、および基本的な構成情報のみを使用した有用なさまざまな処理を実行できます。 一方、API Management ポリシーでは外部サービスと通信することもできるため、さらに可能性が広がります。
前の記事では、 ログ記録、監視、分析のために Azure Event Hubs サービスと対話する方法について説明しました。 この記事では、外部の任意の HTTP ベースのサービスと通信するポリシーのデモを行います。 こうしたポリシーは、リモート イベントをトリガーしたり、元の要求と応答を何らかの方法で操作する情報を取得したりする場合に使用できます。
Send-One-Way-Request
おそらく最も簡単な外部対話は、外部サービスに重要なイベントを通知するためのファイア アンド フォーゲット スタイルの要求です。 制御フロー ポリシー choose を使用して、関心のある任意の種類の条件を検出できます。 条件が満たされると、send-one-way-request ポリシーを使用して外部 HTTP 要求を行うことができます。 この要求は、Hipchat や Slack などのメッセージング システム、SendGrid や MailChimp などのメール API、または PagerDuty などの重要なサポート インシデントに対する要求です。 こうしたメッセージング システムすべてにシンプルな HTTP API があり、呼び出すことができます。
Slack を使用した警告
以下の例では、HTTP 応答のステータス コードが 500 以上の場合に、Slack チャット ルームにメッセージを送信する方法を示します。 500 の範囲エラーは、API のクライアントがそれ自体を解決できないバックエンド API の問題を示します。 通常、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 には、着信 Web フックの概念があります。 受信 Web フックを構成すると、Slack は特別な URL を生成します。これにより、基本的な POST 要求を実行し、Slack チャネルにメッセージを渡すことができます。 作成する JSON 本文は、Slack によって定義されている形式に基づきます。
ファイア アンド フォーゲットは十分か
ファイア アンド フォーゲット スタイルの要求には、あるトレードオフがあります。 何らかの理由で要求が失敗した場合、エラーは報告されません。 このような状況では、2 次障害報告システムの複雑さと、応答を待機するための追加のパフォーマンス コストは保証されません。 応答を確認することが不可欠なシナリオでは、 要求の送信 ポリシーが適しています。
send-request
send-request ポリシーは、外部サービスを使用し複雑な処理機能を実行したり、さらにポリシーを処理したりするために API Management サービスにデータを返すことに使用できます。
参照トークンの承認
API Management の主な機能には、バックエンド リソースの保護があります。 API によって使用される承認サーバーが、Microsoft Entra ID と同様に、OAuth2 フローの一部として JSON Web トークン (JWT) を作成する場合は、validate-jwt ポリシーまたはvalidate-azure-ad-token ポリシーを使用してトークンの有効性を確認できます。 一部の承認サーバーでは、認証サーバーへのコールバックを行わないと検証できない 参照トークン と呼ばれるものを作成します。
標準化されたイントロスペクション
以前は、承認サーバーを使用して参照トークンを検証する標準化された方法はありませんでした。 ただし、インターネット エンジニアリング タスク フォース (IETF) は最近、リソース サーバーがトークンの有効性を検証する方法を定義する、提案された標準 RFC 7662 を公開しました。
トークンの抽出
まず、トークンを Authorization ヘッダーから抽出します。 ヘッダー値は、Bearer に従って、 承認スキーム、単一スペース、および承認トークンを使用して形式を指定する必要があります。 残念ながら、認証スキームが省略されている場合もあります。 解析時にこの省略を考慮するために、API Management はヘッダー値をスペースに分割し、返された文字列の配列から最後の文字列を選択します。 このメソッドは、正しく書式設定されていない承認ヘッダーの回避策を提供します。
<set-variable name="token" value="@(context.Request.Headers.GetValueOrDefault("Authorization","scheme param").Split(' ').Last())" />
検証要求の実行
承認トークンを取得した API Management は、トークンの検証要求を実行できます。 RFC 7662 では、この処理をイントロスペクションと呼び、イントロスペクション リソースに HTML フォームを POST することを求めています。 HTML フォームには、キー tokenと共にキーと値のペアが少なくとも 1 つ含まれている必要があります。 悪意のあるクライアントが有効なトークンを探し出できないようにするために、承認サーバーに対するこの要求も認証する必要があります。
<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>
応答の確認
response-variable-name属性は、返された応答へのアクセスを許可するために使用されます。 このプロパティで定義されている名前は、context.Variables オブジェクトにアクセスする IResponse ディクショナリへのキーとして使用できます。
応答オブジェクトから本文を取得できます。RFC 7622 は、応答は JSON オブジェクトである必要があり、ブール値である active というプロパティが少なくとも 1 つ必要であることを API Management に示します。
activeが true の場合、トークンは有効と見なされます。
または、承認サーバーにトークンが有効かどうかを示す "active" フィールドが含まれていない場合は、 curl などの HTTP クライアント ツールを使用して、有効なトークンに設定されているプロパティを確認します。 たとえば、有効なトークン応答に "expires_in" というプロパティが含まれている場合は、このプロパティ名が認証サーバーの応答に次の方法で存在するかどうかを確認します。
<when condition="@(((IResponse)context.Variables["tokenstate"]).Body.As<JObject>().Property("expires_in") == null)">
レポートのエラー
<choose> ポリシーを使用すると、トークンの有効性を検出できます。無効である場合は、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>
トークンの使用方法を説明する RFC 6750 に従って、API Management は 401 応答を含む ヘッダーも返します。 WWW-Authenticate は、正しく認証される要求を構築する方法をクライアントに指示することを目的としています。 OAuth2 フレームワークではさまざまなアプローチが可能であるため、必要なすべての情報を伝えるのが困難です。 さいわいにも、 クライアントが正しくリソース サーバーに要求を承認させる方法を支援する取り組みが進行中です。
最終的なソリューション
これらをすべてまとめると、次のポリシーがあることになります。
<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>
この例は、api Management サービスを介してフローする要求と応答のプロセスに便利な外部サービスを統合するために、 send-request ポリシーを使用する方法を示す多くの例のうちの 1 つにすぎません。
応答の構成
send-request ポリシーは、前の例で示したように、バックエンド システムへのプライマリ要求を拡張するために使用することも、バックエンド呼び出しを完全に置き換えるために使用することもできます。 このテクニックを使用すると、複数の異なるシステムからまとめた複合リソースを簡単に作成できます。
ダッシュボードの構築
ダッシュボードを動かすためなど、複数のバックエンド システムに存在する情報を公開したい場合があります。 主要業績評価指標 (KPI) は、すべての異なるバックエンドから取得されますが、それらに直接アクセスすることはお勧めしません。 それでも、すべての情報を 1 つの要求で取得できれば便利です。 一部のバックエンド情報は、細分化したり、好ましくない部分は削除したりする必要があるでしょう。 その複合リソースをキャッシュできることは、ユーザーがパフォーマンスの低いメトリックが変更される可能性があるかどうかを確認するために F5 キーを打つ習慣があることがわかっているため、バックエンドの負荷を減らすのに役立ちます。
リソースのフェイク
ダッシュボード リソースを構築するには、まず Azure Portal で新しい操作を構成します。 このプレースホルダー操作は、動的リソースを構築するコンポジション ポリシーを構成するために使用されます。
要求の作成
操作が作成されたら、その操作専用のポリシーを構成できます。
このためには、バックエンドに送信できるよう、まず受信要求のすべてのクエリ パラメーターを抽出します。 この例では、ダッシュボードには、期間に基づいて情報が表示されているので、fromDate と toDate のパラメーターがあります。 要求 URL から情報を抽出するには、set-variable ポリシーを使用します。
<set-variable name="fromDate" value="@(context.Request.Url.Query["fromDate"].Last())">
<set-variable name="toDate" value="@(context.Request.Url.Query["toDate"].Last())">
この情報を入手できたら、バックエンド システムすべてに要求を実行できます。 各要求は、パラメーター情報を使用して新しい URL を構築し、それぞれのサーバーを呼び出し、コンテキスト変数に応答を格納します。
<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 は、これらの要求を順番に送信します。
応答
複合応答を構築するには、return-response ポリシーを使用します。
set-body 要素では式を使用して、すべてのコンポーネント表現がプロパティとして埋め込まれた、新しい JObject を構築できます。
<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>
完全なポリシーは以下のようになります。
<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>
まとめ
Azure API Management サービスには、HTTP トラフィックに選択的に適用できる、バックエンド サービスの構成に使用できる、柔軟なポリシーがあります。 警告機能、確認、検証機能で API ゲートウェイを拡張したい場合、または複数のバックエンド サービスを使用し新しい複合リソースを作成したい場合のために、 send-request と関連ポリシーはさまざまな可能性を開きます。