使用托管标识从 Azure 应用服务调用图
我有一个 .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:
... 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
好的,我这里有有效的代码。发布以防万一对其他人有帮助,或者万一有人可以批评它并使其变得更好。
本质上,我已经不再使用
.AddMicrosoftGraphAppOnly()
来添加GraphServiceClient
,而是启动一个ChainedTokenCredential
来检查 ClientSecret设置,但也会尝试使用托管身份(如果存在)。我使用该凭证对象来获取访问令牌,并在其生命周期内对其进行缓存。
当从 Visual Studio 运行且用户机密中包含 ClientSecret 时,以及在未设置 ClientSecret 的 Azure 应用服务中运行时,此功能有效。
希望这对某人有帮助!如果您认为代码可以改进,请告诉我!
(编辑 - 我已经摆脱了令牌缓存代码。我认为 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 theGraphServiceClient
, and am instead spinning up aChainedTokenCredential
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!
(Edit - I've gotten rid of the token caching code. I think the TokenCredential objects do that for me.)