使用托管标识从 Azure 应用服务调用图

发布于 2025-01-16 15:53:08 字数 4166 浏览 2 评论 0原文

我有一个 .NET 6 Web API 项目,它将 Graph 调用为应用程序(而不是代表用户)。

我正在使用 Microsoft.Identity.Web 包,我的 Program.cs 有以下代码来初始化它:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftGraphAppOnly(auth => new Microsoft.Graph.GraphServiceClient(auth))
    .AddInMemoryTokenCaches();

我已向 Azure AD 应用程序注册授予必要的图形应用程序权限(本例中为 User.Read.All)并授予管理员同意。

当我在本地调试时,我在用户机密中设置了 ClientSecret,并且以下调用很有效:

var photo = await _graph.Users[id].Photo.Content.Request().GetAsync();

但是,当我部署到 Azure 应用服务时,我不想使用 ClientSecret。我想使用我的应用服务的系统分配托管标识。

Microsoft.Identity.Web 显然支持 UserAssignedManagedIdentityClientId 选项,因此我已将环境变量设置为我的应用服务的 ClientID:

...这似乎被代码所接收。

但是,当我尝试执行图形调用时,出现错误。堆栈跟踪看起来像这样(敏感信息已编辑):

An unhandled exception has occurred while executing the request.

Exception: 
Status Code: 0
Microsoft.Graph.ServiceException: Code: generalException
Message: An error occurred sending the request.

 ---> MSAL.NetCore.4.42.0.0.MsalServiceException: 
    ErrorCode: invalid_request
Microsoft.Identity.Client.MsalServiceException: AADSTS70021: No matching federated identity record found for presented assertion. Assertion Issuer: 'https://login.microsoftonline.com/{tenant}/v2.0'. Assertion Subject: '{msi-objectid}'. Assertion Audience: 'fb60f99c-7a34-4190-8149-302f77469936'. https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation
Trace ID: 7862bb35-0bdd-40c9-9c7b-cb12a8a0f200
Correlation ID: 97f749c8-f91d-434e-9ecd-111c65037399
Timestamp: 2022-03-23 05:11:08Z
   at Microsoft.Identity.Client.Internal.Requests.RequestBase.HandleTokenRefreshErrorAsync(MsalServiceException e, MsalAccessTokenCacheItem cachedAccessTokenItem)
   at Microsoft.Identity.Client.Internal.Requests.ClientCredentialRequest.ExecuteAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.ApiConfig.Executors.ConfidentialClientExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenForClientParameters clientParameters, CancellationToken cancellationToken)
   at Microsoft.Identity.Web.TokenAcquisitionAuthenticationProvider.AuthenticateRequestAsync(HttpRequestMessage request)
   at Microsoft.Graph.AuthenticationHandler.SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Microsoft.Graph.HttpProvider.SendRequestAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
    StatusCode: 400 
    ResponseBody: {"error":"invalid_request","error_description":"AADSTS70021: No matching federated identity record found for presented assertion. Assertion Issuer: 'https://login.microsoftonline.com/{tenant}/v2.0'. Assertion Subject: '{msi-objectid}'. Assertion Audience: 'fb60f99c-7a34-4190-8149-302f77469936'. https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation\r\nTrace ID: 7862bb35-0bdd-40c9-9c7b-cb12a8a0f200\r\nCorrelation ID: 97f749c8-f91d-434e-9ecd-111c65037399\r\nTimestamp: 2022-03-23 05:11:08Z","error_codes":[70021],"timestamp":"2022-03-23 05:11:08Z","trace_id":"7862bb35-0bdd-40c9-9c7b-cb12a8a0f200","correlation_id":"97f749c8-f91d-434e-9ecd-111c65037399","error_uri":"https://login.microsoftonline.com/error?code=70021"} 
    Headers: Cache-Control: no-store, no-cache

这里有趣的是,堆栈跟踪错误中的“断言主题”是我的托管身份的ObjectID,而不是ClientID< /em>.所以看起来它已经正确找到了应用程序服务,但碰壁了。

有人能做到这一点吗?我可以使用我的应用服务的系统管理标识来调用 Graph 吗?

I have a .NET 6 web API project that calls Graph as the app (rather than on behalf of the user).

I'm using the Microsoft.Identity.Web package, and my Program.cs has this code to initialise it:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftGraphAppOnly(auth => new Microsoft.Graph.GraphServiceClient(auth))
    .AddInMemoryTokenCaches();

I've given the Azure AD App Registration the necessary Graph app permissions (User.Read.All in this example) and granted admin consent.

When I'm debugging locally, I've set a ClientSecret in my User Secrets and the following call works a treat:

var photo = await _graph.Users[id].Photo.Content.Request().GetAsync();

However, when I'm deployed to an Azure App Service, I don't want to use a ClientSecret. I want to use the System-Assigned Managed Identity of my App Service.

Microsoft.Identity.Web apparently supports a UserAssignedManagedIdentityClientId option, so I have set an environment variable to the ClientID of my App Service:

UserAssignedManagedIdentityClientID environment variable

... and that does seem to be getting picked up by the code.

However, when I try to execute the graph call, I get an error. The stack trace looks like this (sensitive info redacted):

An unhandled exception has occurred while executing the request.

Exception: 
Status Code: 0
Microsoft.Graph.ServiceException: Code: generalException
Message: An error occurred sending the request.

 ---> MSAL.NetCore.4.42.0.0.MsalServiceException: 
    ErrorCode: invalid_request
Microsoft.Identity.Client.MsalServiceException: AADSTS70021: No matching federated identity record found for presented assertion. Assertion Issuer: 'https://login.microsoftonline.com/{tenant}/v2.0'. Assertion Subject: '{msi-objectid}'. Assertion Audience: 'fb60f99c-7a34-4190-8149-302f77469936'. https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation
Trace ID: 7862bb35-0bdd-40c9-9c7b-cb12a8a0f200
Correlation ID: 97f749c8-f91d-434e-9ecd-111c65037399
Timestamp: 2022-03-23 05:11:08Z
   at Microsoft.Identity.Client.Internal.Requests.RequestBase.HandleTokenRefreshErrorAsync(MsalServiceException e, MsalAccessTokenCacheItem cachedAccessTokenItem)
   at Microsoft.Identity.Client.Internal.Requests.ClientCredentialRequest.ExecuteAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.ApiConfig.Executors.ConfidentialClientExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenForClientParameters clientParameters, CancellationToken cancellationToken)
   at Microsoft.Identity.Web.TokenAcquisitionAuthenticationProvider.AuthenticateRequestAsync(HttpRequestMessage request)
   at Microsoft.Graph.AuthenticationHandler.SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Microsoft.Graph.HttpProvider.SendRequestAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
    StatusCode: 400 
    ResponseBody: {"error":"invalid_request","error_description":"AADSTS70021: No matching federated identity record found for presented assertion. Assertion Issuer: 'https://login.microsoftonline.com/{tenant}/v2.0'. Assertion Subject: '{msi-objectid}'. Assertion Audience: 'fb60f99c-7a34-4190-8149-302f77469936'. https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation\r\nTrace ID: 7862bb35-0bdd-40c9-9c7b-cb12a8a0f200\r\nCorrelation ID: 97f749c8-f91d-434e-9ecd-111c65037399\r\nTimestamp: 2022-03-23 05:11:08Z","error_codes":[70021],"timestamp":"2022-03-23 05:11:08Z","trace_id":"7862bb35-0bdd-40c9-9c7b-cb12a8a0f200","correlation_id":"97f749c8-f91d-434e-9ecd-111c65037399","error_uri":"https://login.microsoftonline.com/error?code=70021"} 
    Headers: Cache-Control: no-store, no-cache

The interesting thing here is that the "Assertion Subject" in the stack trace erorr is the ObjectID of my managed identity, not the ClientID. So it seems like it has found the App Service correctly but hitting a wall.

Has anyone been able to pull this off? Can I call Graph using the system managed identity of my app service?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

凉月流沐 2025-01-23 15:53:08

好的,我这里有有效的代码。发布以防万一对其他人有帮助,或者万一有人可以批评它并使其变得更好。

本质上,我已经不再使用 .AddMicrosoftGraphAppOnly() 来添加 GraphServiceClient,而是启动一个 ChainedTokenCredential 来检查 ClientSecret设置,但也会尝试使用托管身份(如果存在)。

我使用该凭证对象来获取访问令牌,并在其生命周期内对其进行缓存。

当从 Visual Studio 运行且用户机密中包含 ClientSecret 时,以及在未设置 ClientSecret 的 Azure 应用服务中运行时,此功能有效。

希望这对某人有帮助!如果您认为代码可以改进,请告诉我!

builder.Services.AddSingleton<TokenCredential>(services =>
{
    var options = new MicrosoftIdentityOptions();
    builder.Configuration.Bind("AzureAd", options);

    var creds = new List<TokenCredential> { new ManagedIdentityCredential() };
    if (!string.IsNullOrEmpty(options.ClientSecret))
    {
        creds.Add(new ClientSecretCredential(options.TenantId, options.ClientId, options.ClientSecret));
    }
    return new ChainedTokenCredential(creds.ToArray());
});

// Add services to the container.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddScoped(services => new GraphServiceClient(services.GetRequiredService<TokenCredential>()));

(编辑 - 我已经摆脱了令牌缓存代码。我认为 TokenCredential 对象可以为我做到这一点。)

OK, I have code here that works. Posting in case it helps others or in case someone can critique it and make it better.

Essentially I have moved away from using .AddMicrosoftGraphAppOnly() to add the GraphServiceClient, and am instead spinning up a ChainedTokenCredential which checks for a ClientSecret in the settings but also tries to use a Managed Identity if one exists.

I'm using that credential object to fetch an access token, and caching it for its lifetime.

This is working when running from Visual Studio with a ClientSecret in the user secrets, and when running in an Azure App Service with no ClientSecret set.

Hope this helps someone! Let me know if you think the code can be improved!

builder.Services.AddSingleton<TokenCredential>(services =>
{
    var options = new MicrosoftIdentityOptions();
    builder.Configuration.Bind("AzureAd", options);

    var creds = new List<TokenCredential> { new ManagedIdentityCredential() };
    if (!string.IsNullOrEmpty(options.ClientSecret))
    {
        creds.Add(new ClientSecretCredential(options.TenantId, options.ClientId, options.ClientSecret));
    }
    return new ChainedTokenCredential(creds.ToArray());
});

// Add services to the container.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddScoped(services => new GraphServiceClient(services.GetRequiredService<TokenCredential>()));

(Edit - I've gotten rid of the token caching code. I think the TokenCredential objects do that for me.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文