使用 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
- Azure CLI
- Azure 函数核心工具
- .Net Core 3.1 SDK
- 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
}
不过
上面的代码很快就会导致套接字耗尽。正确的方法是使用 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) { ... }
}
备注
需要注意的是,上面的代码没有处理其他重要问题,例如:
- 错误处理
- 令牌缓存
- 能够为不同的 API 端点设置不同的 HttpClients 和 MessageHandlers。
错误处理程序应该很容易添加。其余的超出了这个问题的范围。
我需要从另一个 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
- Azure CLI
- Azure 函数核心工具
- .Net Core 3.1 SDK
- 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
}
不过
上面的代码很快就会导致套接字耗尽。正确的方法是使用 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) { ... }
}
备注
需要注意的是,上面的代码没有处理其他重要问题,例如:
- 错误处理
- 令牌缓存
- 能够为不同的 API 端点设置不同的 HttpClients 和 MessageHandlers。
错误处理程序应该很容易添加。其余的超出了这个问题的范围。