適用於:所有 APIM 層
Azure API 管理服務中可用的原則可純粹根據傳入的要求、傳出的回應及基本組態資訊來進行各式各樣的有用工作。 不過,能夠與來自 API 管理原則的外部服務進行互動,可開啟更多的機會。
在先前的文章中,您已瞭解如何與 Azure 事件中樞服務互動以進行記錄、監視和分析。 本文將示範可讓您與任何以 HTTP 為基礎的外部服務進行互動的原則。 這些原則可用來觸發遠端事件,或用來擷取將以某種方式用於操作原始要求和回應的資訊。
傳送單向要求
可能最簡單的外部互動是一種「即傳即弃」的請求方式,這允許外部服務接收到某種重要事件的通知。 控制流程原則 choose 可用來偵測您感興趣的任何類型的條件。 如果符合條件,您可以使用 send-one-way-request 原則進行外部 HTTP 要求。 此請求可能是針對 Hipchat 或 Slack 等訊息系統,或是 SendGrid 或 MailChimp 等郵件 API,或針對 PagerDuty 等關鍵支援事件。 這些傳訊系統權都具有簡單的 HTTP API,可供輕鬆叫用。
使用 Slack 提供警示
下列範例示範如果 HTTP 回應狀態碼大於或等於 500,如何將訊息傳送至 Slack 聊天室。 500 範圍錯誤表示後端 API 有問題,API 的用戶端無法自行解決。 它通常需要 API 管理 部分的某種干預。
<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 所定義的格式為基礎。
「射後不理」 夠好嗎?
使用要求的射後不理樣式有一些特定的權衡取捨。 如果由於某種原因,要求失敗,則不會報告失敗。 在這種情況下,次要故障報告系統的複雜性以及等待回應的額外效能成本是沒有保證的。 對於必須檢查回應的案例,則 傳送要求 原則是更好的選擇。
send-request
send-request 原則能夠使用外部服務來執行複雜的處理函式,並將資料傳回 API 管理服務,此服務可用來進一步處理原則。
授權參考權杖
API 管理的主要功能是保護後端資源。 如果您的 API 使用的授權伺服器在其 OAuth2 流程中生成 JSON Web 權杖(JWT),如同 Microsoft Entra ID 那樣,那麼您可以使用 validate-jwt 原則或 validate-azure-ad-token 原則來驗證權杖的有效性。 某些授權伺服器會建立所謂的 參考權杖 ,如果不向授權伺服器回呼,則無法驗證這些權杖。
將自我檢查標準化
過去一直沒有標準化的方式可使用授權伺服器來驗證參考權杖。 然而,互聯網工程任務組 (IETF) 最近發布了擬議的標準 RFC 7662 ,該標準定義了資源服務器如何驗證令牌的有效性。
擷取權杖
第一個步驟是從授權標頭擷取權杖。 根據 Bearer,標頭值應依序使用 授權配置、單一空格和授權權杖進行格式化。 不過,有一些情況需要省略授權配置。 為了在剖析時考慮此遺漏,API 管理 會分割空格上的標頭值,並從傳回的字串陣列中選取最後一個字串。 此方法為格式不正確的授權標頭提供因應措施。
<set-variable name="token" value="@(context.Request.Headers.GetValueOrDefault("Authorization","scheme param").Split(' ').Last())" />
提出驗證要求
一旦 API 管理擁有授權權杖之後,API 管理就可以提出要求來驗證權杖。 RFC 7662 會呼叫此程序自我檢查,並要求您將 HTML 表單 POST 到自我檢查資源。 HTML 表單至少必須包含具有索引鍵 token的索引鍵/值組。 這項對授權伺服器的要求也必須經過驗證,以確保惡意用戶端無法撈取有效的權杖。
<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 向 API 管理指出回應必須是 JSON 物件,而且必須包含至少一個稱為 active 的屬性 (此為布林值)。 當 active 為 true,權杖會被視為有效。
或者,如果授權伺服器未包含 "active" 欄位來指出權杖是否有效,請使用 HTTP 用戶端工具,例如 curl 判斷在有效權杖中設定的內容。 例如,如果有效的記號回應包含名為 "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>
根據說明應如何使用 權杖的 bearer (英文),APIM 也會傳回 WWW-Authenticate 標頭以及 401 回應。 WWW 驗證的目的是指示用戶端如何建構適當授權的要求。 由於 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>
此範例只是示範如何使用 send-request 原則將有用的外部服務整合到流經 API 管理 服務的要求和回應程式中的眾多範例之一。
回應組合
此 send-request 原則可用於增強對後端系統的主要要求,如上一個範例所示,也可以用來完全取代後端呼叫。 使用這項技術,您可以輕鬆地建立彙總自多個不同系統的複合資源。
建置儀表板
有時您想要能夠公開存在於多個後端系統中的資訊,例如,驅動儀表板。 關鍵績效指標 (KPI) 來自所有不同的後端,但您不希望提供對它們的直接存取。 不過,如果可以在一次請求中檢索所有信息,那就太好了。 或許有一些後端資訊需要進行某些切割與細分,需要先稍微處理一下! 能夠快取該複合資源將是減少後端負載的有用方法,因為您知道使用者有按 F5 鍵的習慣,以查看其表現不佳的指標是否可能會發生變化。
假造資源
建置儀表板資源的第一個步驟,是在 Azure 入口網站中設定新的作業。 此預留位置作業可用來設定組合原則以建置動態資源。
提出要求
建立作業之後,您可以專門針對該作業設定原則。
第一個步驟是擷取來自傳入要求的任何查詢參數,以便您可將其轉送至後端。 在此範例中,儀表板會根據一段時間來顯示資訊,因此具有 fromDate 和 toDate 參數。 您可以使用 set-variable 原則來擷取要求 URL 中的資訊。
<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 管理 會循序傳送這些要求。
回應
若要建構複合回應,您可以使用 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 管理服務提供彈性的原則,可以選擇性地套用到 HTTP 流量,並且能夠組合後端服務。 不論您是否想要使用警示功能、確認、驗證功能或根據多個後端服務建立新的複合資源來增強您的 API 閘道器, send-request 及相關原則都會開啟各種可能性。