我正在尝试将我的 C# 控制台应用程序(.net core 2.1)连接到 blob 存储。我以几种不同的方式初始化 Blob 存储客户端。他们是:
- 连接字符串- 在开发过程中很有用
- 服务原则- 生产部署的可能性
- MSI 身份验证- 更安全,密钥为我们自动循环
在我的代码中,如果没有明确设置连接字符串,我会根据定义的应用程序设置使用服务原则或 MSI 生成它(下面的示例初始化代码)。无论我使用三种方式中的哪一种,我最终都会使用连接字符串初始化客户端(在 1. 的情况下明确设置或在 2. 和 3. 的情况下使用我的代码生成)。
下面的代码对于 1(连接字符串)和 2(服务原则)可以 100% 正常工作,但是在尝试实现 3(MSI)时出现错误。
在本地运行时出现此错误:
访问令牌来自错误的颁发者“ https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/ ”。它必须与与此订阅关联的租户“ https://sts.windows.net/{my-subscription-id}/ ”匹配。请使用授权 (URL) ' https://login.windows.net/ {my-subscription-id}' 获取令牌。请注意,如果将订阅转移给另一个租户,则对服务没有影响,但有关新租户的信息可能需要一段时间才能传播(最多一个小时)。如果您刚刚转移订阅并看到此错误消息,请稍后再试。
有了这个,我不知道'f8cdef31-a31e-4b4a-93e4-5f571e91255a'来自哪里,它可能是全球微软实例。我试图通过在启用了 MSI 的 Azure 中的 webjob 中运行我的代码来缓解这种情况,以查看它是否有效,我得到:
System.AggregateException:发生一个或多个错误。(服务连接发生异常,详见内部异常)---> System.Exception:服务连接发生异常,详见内部异常---> Microsoft.Rest.Azure.CloudException:客户端对象 ID 为“{my-subscription-id}”的“{my-subscription-id}”无权在“/subscriptions/{my-subscription-id}”范围内执行操作“Microsoft.Storage/storageAccounts/read” '。
(请注意,我将 MSI 帐户设置为 blob 存储的“所有者”和“存储帐户密钥操作员”)
我通过以下方式初始化 CloudStorageAccount 客户端:
public void InitializeClient()
{
// Always using the connection string, no matter how it's generated.
if (ConnectionString.IsNullOrEmpty()) // if not already set, then build.
ConnectionString = BuildStorageConnection().GetAwaiter().GetResult();
CloudStorageAccount.TryParse(ConnectionString, out var storageAccount);
if (storageAccount == null)
throw new InvalidOperationException("Cannot find storage account");
// CloudBlobClient that represents the Blob storage endpoint.
_cloudBlobClient = storageAccount.CreateCloudBlobClient();
}
并建立一个连接字符串如下:
internal async Task<string> BuildStorageConnection()
{
try
{
string token = null;
if (Config.UseMsi)
{
// Managed Service Identity (MSI) authentication.
var provider = new AzureServiceTokenProvider();
token = provider.GetAccessTokenAsync("https://management.azure.com/").GetAwaiter().GetResult();
if (string.IsNullOrEmpty(token))
throw new InvalidOperationException("Could not authenticate using Managed Service Identity");
_expiryTime = DateTime.Now.AddDays(1);
}
else
{
// Service Principle authentication
// Grab an authentication token from Azure.
var context = new AuthenticationContext("https://login.windows.net/" + Config.TenantId);
var credential = new ClientCredential(Config.AppId, Config.AppSecret);
var tokenResult = context.AcquireTokenAsync("https://management.azure.com/", credential).GetAwaiter().GetResult();
if (tokenResult == null || tokenResult.AccessToken == null)
throw new InvalidOperationException($"Could not authenticate using Service Principle");
_expiryTime = tokenResult.ExpiresOn;
token = tokenResult.AccessToken;
}
// Set credentials and grab the authenticated REST client.
var tokenCredentials = new TokenCredentials(token);
var client = RestClient.Configure()
.WithEnvironment(AzureEnvironment.AzureGlobalCloud)
.WithLogLevel(HttpLoggingDelegatingHandler.Level.BodyAndHeaders)
.WithCredentials(new AzureCredentials(tokenCredentials, tokenCredentials, string.Empty, AzureEnvironment.AzureGlobalCloud))
.WithRetryPolicy(new RetryPolicy(new HttpStatusCodeErrorDetectionStrategy(), new FixedIntervalRetryStrategy(3, TimeSpan.FromMilliseconds(500))))
.Build();
// Authenticate against the management layer.
var azureManagement = Azure.Authenticate(client, string.Empty).WithSubscription(Config.SubscriptionId);
// Get the storage namespace for the passed in instance name.
var storageNamespace = azureManagement.StorageAccounts.List().FirstOrDefault(n => n.Name == Config.StorageInstanceName);
// If we cant find that name, throw an exception.
if (storageNamespace == null)
{
throw new InvalidOperationException($"Could not find the storage instance {Config.StorageInstanceName} in the subscription with ID {Config.SubscriptionId}");
}
// Storage accounts use access keys - this will be used to build a connection string.
var accessKeys = await storageNamespace.GetKeysAsync();
// If the access keys are not found (not configured for some reason), throw an exception.
if (accessKeys == null)
{
throw new InvalidOperationException($"Could not find access keys for the storage instance {Config.StorageInstanceName}");
}
// We just default to the first key.
var key = accessKeys[0].Value;
// Build and return the connection string.
return $"DefaultEndpointsProtocol=https;AccountName={Config.StorageInstanceName};AccountKey={key};EndpointSuffix=core.windows.net";
}
catch (Exception e)
{
Logger?.LogError(e, "An exception occured during connection to blob storage");
throw new Exception("An exception occurred during service connection, see inner exception for more detail", e);
}
}
我获取访问令牌的主要区别在于,使用服务原则我有一个身份验证上下文,而使用 MSI 我没有。这会影响身份验证的范围吗?非常感谢任何帮助和建议!