使用 DefaultAzureCredential 和 HttpClient 的 Azure Function 到 Azure Function 请求

Azure Function To Azure Function Request using DefaultAzureCredential and HttpClient

我需要从另一个 Azure 函数调用一个 Http Azure 函数。

目前,我调用 Azure Key Vault 来获取目标函数的密钥,并将其放入 URL 中,如下所述:https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=csharp#api-key-authorization

但是,我想开始使用托管标识和 DefaultAzureCredential,但我找不到如何将 DefaultAzureCredential 与 HttpClient 或类似工具一起使用。

如何使用 DefaultAzureCredential 和 HttpClient 从另一个函数调用一个函数?

在带有 Http 的 Azure 函数中使用 DefaultAzureCredentials 的先决条件 Client/Request

  1. Azure CLI
  2. Azure 函数核心工具
  3. .Net Core 3.1 SDK
  4. Terraform CLI

以下用于创建资源并将其部署到 Azure 的步骤:

1。将资源部署到 Azure:

 az login
az account set --subscription < target subscription ID>
cd terraform
terraform apply --var basename="< resourcename>" --var resource_group_name="<resource group name>" --var location="<Azure region name>"

2。将函数应用程序部署到 Azure:

它会询问您是否安装了多个 Functions 核心工具版本(如 v1、v2、v3)。部署完成后,将在 bash shell 中创建并执行 deploy.app.sh 文件,从而将 Function App 完全部署到 Azure。

3。观察Function App的配置

观察Portal中Function App的配置菜单下的连接字符串,其中包含关联的存储帐户连接字符串,其中此存储帐户包含发布时上传的.zip包的Functions文件内容。

4。使用托管身份滚动存储帐户中的密钥

在 Azure 门户的 Function App > Identity > System Assigned 下(将状态切换为 ON)并单击 Save。

无需重新启动函数应用即可开始使用存储帐户中的新密钥。

执行以下命令生成SAS URL:

curl --location --request GET 'https://fxnxxxxxx.azurewebsites.net/api/GetSASUrl?code=3TR6xxxxxx&blobUri=https://fxxxx.blob.core.windows.net/sample/my.file'

此处 blobUri 是目标 blob 的完整 Http URL。

要下载 blob,请在 InPrivate 浏览器中点击 URL:

https://fxn_____.blob.core.windows.net/sample/my.file?skoid=......pxLSpVwuML%2B3UXrxBmC6XGA%3D

以下命令获取 存储帐户密钥:

curl --location --request GET 'https://fxnxxxxxxx.azurewebsites.net/api/GetAccountKeys?code=GKUxxxxxxxx&accountName=fxnxxxx`

accountName 是存储帐户名称,相当于 basename Terraform 命令上面传入的变量。

  • 响应来自 JSON 格式包含与 keyName、[=56] 相关的 key-value 对=]权限.

作为正常安全协议的一部分,通过转到 Azure 门户 > 存储帐户 > 访问密钥 > 重新生成来重新生成存储帐户密钥 .

检查 使用此命令更改密钥:

curl --location --request POST 'https://fxnxxxxxx.azurewebsites.net/api/RegenerateKey?code=9OZxxxxx&accountName=fxnstormsisampsc&keyName=key2`

响应将在 Http 状态代码 200 OK 中并检查 Azure 门户 > 存储帐户 > 访问密钥。

此处引用code and documentation.

解决这个问题的简单方法是这样的:

var targetFunctionAppAppRegistrationApplicationId = "A Guid that you must get from your target Function's Authentication configuration - 'App (client) ID'";
var url = "https://yourfunctionappname.azurewebsites.net/api/targetfunctionname";
var creds = new DefaultAzureCredential();
var token = await creds.GetTokenAsync(new Azure.Core.TokenRequestContext(new[] { targetFunctionAppAppRegistrationApplicationId }));
using (HttpClient client = new HttpClient())
{
  client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
  var result = await client.GetAsync(url);
  // Anything else you want to do with the result
}

以上内容归功于https://spblog.net/post/2021/09/28/call-azure-ad-secured-azure-function-from-logic-app-or-another-function-with-managed-identity

不过

上面的代码很快就会导致套接字耗尽。正确的方法是使用 HttpClientFactory,如下所述:https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests

由于这些文档中未涵盖此特定用例,下面是一个示例。

首先,您需要一个 MessageHandler:

public class AzureDefaultCredentialsAuthorizationMessageHandler : DelegatingHandler
{
  private readonly TokenRequestContext TokenRequestContext;
  private readonly DefaultAzureCredential Credentials;

  public AzureDefaultCredentialsAuthorizationMessageHandler()
  {
    // This parameter is actually a list of scopes.
    // If your target Function has defined scopes then you should use them here.
    // TokenRequestContext also supports many other options you should probably check out.
    TokenRequestContext = new (new[] { "targetFunctionAppAppRegistrationApplicationId" }); 
    Credentials = new DefaultAzureCredential();
  }

  protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  {
    var tokenResult = await Credentials.GetTokenAsync(TokenRequestContext, cancellationToken);
    var authorizationHeader = new AuthenticationHeaderValue("Bearer", tokenResult.Token);
    request.Headers.Authorization = authorizationHeader;
    return await base.SendAsync(request, cancellationToken);
  }
}

然后您需要在依赖注入容器中使用此消息处理程序注册一个 HttpClient。如果您使用的是标准 IServiceCollection:

services
  .AddScoped<AzureDefaultCredentialsAuthorizationMessageHandler>()
  .AddHttpClient<YourClassUsingTheHttpClient>((serviceProvider, httpClient) => 
  {
    httpClient.BaseAddress = "https://yourfunctionappname.azurewebsites.net/api/targetfunctionname";
  }).AddHttpMessageHandler<AzureDefaultCredentialsAuthorizationMessageHandler>();

最后,只有一个 YourClassUsingTheHttpClient class 在其构造函数中采用 HttpClient:

public class YourClassUsingTheHttpClient
{
  public YourClassUsingTheHttpClient(HttpClient httpClient) { ... }
}

备注

需要注意的是,上面的代码没有处理其他重要问题,例如:

  1. 错误处理
  2. 令牌缓存
  3. 能够为不同的 API 端点设置不同的 HttpClients 和 MessageHandlers。

错误处理程序应该很容易添加。其余的超出了这个问题的范围。