如何创建ServiceClientCredential并与Microsoft.Azure.Management.Compute一起使用

23
我正在尝试使用 C# 编程以编程方式检索来自 Microsoft.Azure.Management.Compute 的 HostedServices。这需要 ServiceClientCredential,但我不知道如何获取它。请问如何实例化这个类?我能够使用 Microsoft.WindowsAzure.Management.Compute 获取它们,但这里只返回 ResourceManager 下的实例而不是经典实例。

10
确实,由于某些原因,微软决定仅提供有关这些库的最基本文档。这非常令人困惑,特别是因为 Classic 和 Resource Manager 显然(?)在不同的库中处理,具有难以理解的命名空间,例如 Microsoft.Azure.Management.Compute(执行一项操作)与 Microsoft.WindowsAzure.Management.Compute(执行不同操作)。 - Nik
1
我已经试图弄清楚这个问题几天了,但是我阅读他们的文档越多,就越感到困惑! - kuldeep
4个回答

23

首先需要创建Active Directory应用程序。请参见如何:使用门户创建可访问资源的Azure AD应用程序和服务主体

下面的示例代码使用nuget包Microsoft.Azure.Management.Compute 13.0.1-prerelease

public class CustomLoginCredentials : ServiceClientCredentials
{
    private string AuthenticationToken { get; set; }
    public override void InitializeServiceClient<T>(ServiceClient<T> client)
    {
        var authenticationContext = new AuthenticationContext("https://login.windows.net/{tenantID}");
        var credential = new ClientCredential(clientId: "xxxxx-xxxx-xx-xxxx-xxx", clientSecret: "{clientSecret}");

        var result = authenticationContext.AcquireToken(resource: "https://management.core.windows.net/", clientCredential: credential);

        if (result == null) throw new InvalidOperationException("Failed to obtain the JWT token");

        AuthenticationToken = result.AccessToken;
    }
    public override async Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request == null) throw new ArgumentNullException("request");

        if (AuthenticationToken == null) throw new InvalidOperationException("Token Provider Cannot Be Null");
        
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AuthenticationToken);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        //request.Version = new Version(apiVersion);
        await base.ProcessHttpRequestAsync(request, cancellationToken);
    }
}

然后您可以像这样初始化客户端:

netClient = new Microsoft.Azure.Management.Compute.ComputeManagementClient(new CustomLoginCredentials());
netClient.SubscriptionId = _subscriptionId;

7
为什么我们需要在InitializeServiceClient中做这么多事情?看起来最好的方式是创建一个构造函数,该构造函数可以获取必要的资源并生成令牌提供程序,鉴于我们不使用客户端来执行此操作。然后,ProcessHttpRequestAsync可以从身份验证上下文中获取新的令牌(以确保它是最新的令牌)。 - ElFik
1
确实,@ElFik。同步调用AcquireToken已不再可用,因此如果我们不想插入自己的异步阻塞技巧,现在我们必须像你建议的那样将令牌获取移动到RequestAsync调用中。 - shannon

4
现在您可以使用ITokenProvider和Microsoft.Rest.TokenCredentials来实现此操作。
public class CustomTokenProvider : ITokenProvider
{
    private readonly CustomConfiguration _config;

    public CustomTokenProvider(CustomConfiguration config)
    {
        _config = config;
    }

    public async Task<AuthenticationHeaderValue> GetAuthenticationHeaderAsync(CancellationToken cancellationToken)
    {
        // For app only authentication, we need the specific tenant id in the authority url
        var tenantSpecificUrl = $"https://login.microsoftonline.com/{_config.TenantId}/";

        // Create a confidential client to authorize the app with the AAD app
        IConfidentialClientApplication clientApp = ConfidentialClientApplicationBuilder
                                                                        .Create(_config.ClientId)
                                                                        .WithClientSecret(_config.ClientSecret)
                                                                        .WithAuthority(tenantSpecificUrl)
                                                                        .Build();
        // Make a client call if Access token is not available in cache
        var authenticationResult = await clientApp
            .AcquireTokenForClient(new List<string> { _config.Scope })
            .ExecuteAsync();


        return new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
    }
}

接着,在你的依赖注入(DI)配置中:

services.AddTransient<IPowerBIClient, PowerBIClient>((provider) =>
{
    var config = provider.GetRequiredService<CustomConfiguration>();
    var tokenProvider = provider.GetRequiredService<CustomTokenProvider>();

    return new PowerBIClient(new Uri(config.BaseUrl), new TokenCredentials(tokenProvider));
});

我的示例用于Power BI,但可用于需要访问ServiceClientCredentials的任何内容。

您可以使用Nuget包Microsoft.Identity.Client来获取IConfidentialClientApplication。


3

正如 @verbedr 所回答的那样,您可以从 Azure.Identity 客户端库 中适应 TokenCredential。@antdev 也回答了 您可以实现一个 Microsoft.Rest.ITokenProvider。另一种选择是将两种方法结合起来,如下所示:

using Azure.Core;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Rest
{
    /// Allows an Azure.Core.TokenCredential to be the Microsoft.Rest.ITokenProvider.
    public class TokenCredentialTokenProvider : Microsoft.Rest.ITokenProvider
    {
        readonly TokenCredential _tokenCredential;
        readonly string[] _scopes;

        public TokenCredentialTokenProvider(TokenCredential tokenCredential, string[] scopes)
        {
            _tokenCredential = tokenCredential;
            _scopes = scopes;
        }

        public async Task<AuthenticationHeaderValue> GetAuthenticationHeaderAsync(CancellationToken cancellationToken)
        {
            var accessToken = await _tokenCredential.GetTokenAsync(new TokenRequestContext(_scopes), cancellationToken);
            return new AuthenticationHeaderValue("Bearer", accessToken.Token);
        }
    }
}

它没有缓存功能。如果需要,您可以创建一个类似于CachingTokenProvider的缓存令牌提供程序。可以按照以下方式使用它:
            var tokenCredentials = new Azure.Identity.DefaultAzureCredential(new Azure.Identity.DefaultAzureCredentialOptions
            {
                AuthorityHost = Azure.Identity.AzureAuthorityHosts.AzurePublicCloud
            });

            var restTokenProvider = new Microsoft.Rest.TokenCredentialTokenProvider(tokenCredentials,
                new string[] { "https://management.core.windows.net/.default" }
            );

            var restTokenCredentials = new Microsoft.Rest.TokenCredentials(restTokenProvider);

            using var computeClient = new ComputeManagementClient(restTokenCredentials);
            // computeClient.BaseUri = // set if using another cloud
            computeClient.SubscriptionId = subscriptionId;
            var vms = computeClient.VirtualMachines.ListAll();
            Console.WriteLine("# of vms " + vms.Count());

这对我有用。以下是我在csproj中使用的相关依赖项:

    <PackageReference Include="Azure.Identity" Version="1.4.0" />
    <PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.23" />
    <PackageReference Include="Microsoft.Azure.Management.Compute" Version="46.0.0" />

值得一提的是,像Azure.ResourceManager.Compute这样的新设计的Azure.ResourceManager库直接支持Azure.Identity。请参见https://github.com/Azure/azure-sdk-for-net/blob/master/doc/mgmt_preview_quickstart.md - Cameron Taggart

3
稍晚一些,但这是我们在项目中的做法。我们使用由.NET框架提供的令牌凭据来访问托管标识、Visual Studio(Code)标识或交互式标识,并连接到Azure基础结构API。
internal class CustomTokenProvider : ServiceClientCredentials
{
    private const string BearerTokenType = "Bearer";
    private TokenCredential _tokenCredential;
    private readonly string[] _scopes;
    private readonly IMemoryCache _cache;

    public CustomTokenProvider(TokenCredential tokenCredential, string[] scopes, IMemoryCache cache)
    {
        _tokenCredential = tokenCredential ?? throw new ArgumentNullException(nameof(tokenCredential));
        _scopes = scopes ?? throw new ArgumentNullException(nameof(scopes));
        _cache = cache;
    }

    public override async Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request == null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        var token = await _cache.GetOrCreateAsync("accessToken-tokenProvider." + string.Join("#", _scopes), async e =>
        {
            var accessToken = await _tokenCredential.GetTokenAsync(new TokenRequestContext(_scopes), cancellationToken);
            e.AbsoluteExpiration = accessToken.ExpiresOn;
            return accessToken.Token;
        });
        request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(BearerTokenType, token);
        await base.ProcessHttpRequestAsync(request, cancellationToken).ConfigureAwait(false);
    }
}

需要注意的几点:

  • TokenCredential类不会缓存令牌,如果您没有进行缓存,将会因为请求过多而在Azure上触发错误。
  • 使用v2调用调用v1终结点需要在scopes方面有些创意。当您需要访问管理API时,请提供以下范围:“https://management.core.windows.net/.default”,而不是指定的user_impersonate范围。这是由于在不同终结点上进行了一些内部转换。并且'.default'范围始终可用,并将为您提供所需的内容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接