如何为已登录用户获取Microsoft Graph访问令牌?.NET Core+Angular问题
问题分析与解决方案
首先,咱们先搞清楚为什么cca.Users是空的——你当前的代码是基于服务器端渲染(SSR)应用的示例写的,这类应用里用户是直接在后端完成登录的,所以后端的令牌缓存会关联到用户的服务器会话。但你的场景是Angular SPA + .NET Core Web API,用户登录是在前端完成的,后端并没有维护用户的登录会话,自然找不到用户缓存,所以才会抛出这个异常。
针对你的场景,正确的做法是使用On-Behalf-Of(OBO)流程:前端先获取访问后端API的令牌,后端拿到这个令牌后,以此为“断言”向Microsoft Identity Platform申请代表用户访问Graph的令牌,而不是依赖后端的用户令牌缓存。
一、修复后端获取用户Graph令牌的方法(改用OBO流程)
替换你原来的GetUserAccessTokenAsync方法,改成以下实现:
using Microsoft.Identity.Client; using System.Net.Http.Headers; using System.Threading.Tasks; public async Task<string> GetUserAccessTokenAsync(string userAccessToken) { var cca = new ConfidentialClientApplication( _clientId, _clientAuthority, _redirectUri, _credential, null, null); try { // 使用OBO流程,以前端传来的用户令牌作为断言 var result = await cca.AcquireTokenOnBehalfOfAsync( _scopes, new UserAssertion(userAccessToken)); return result.AccessToken; } catch (MsalException ex) { throw new ServiceException(new Error { Code = GraphErrorCode.AuthenticationFailure.ToString(), Message = $"OBO流程获取令牌失败: {ex.Message}" }); } }
关键说明:
- 你需要从后端API的请求头中提取前端传来的用户令牌(
Authorization: Bearer <token>),然后把这个令牌传入该方法。 - 确保你的Azure AD应用注册中,已经给后端API添加了委派权限(比如Graph的
User.Read等),并完成了管理员同意。 - 要在后端API的配置中启用JWT验证,确保前端传来的令牌是有效的。
二、Angular前端调用后端API的示例(含令牌传递)
假设你用MSAL.js(Angular 5对应msal-angular v1.x)完成前端登录,登录后获取访问后端API的令牌,然后在调用后端时把令牌放在请求头里:
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { MsalService } from '@azure/msal-angular'; @Component({ /* 组件配置 */ }) export class YourComponent { constructor(private http: HttpClient, private msalService: MsalService) {} async callBackendAndGraph() { // 获取访问后端API的令牌 const accessToken = await this.msalService.acquireTokenSilent({ scopes: ["api://<你的后端API应用ID>/access_as_user"] // 后端API的权限范围 }); // 构建请求头,携带令牌 const headers = new HttpHeaders({ 'Authorization': `Bearer ${accessToken}` }); // 调用后端API const response = await this.http.get('/api/graph/user', { headers }).toPromise(); console.log(response); } }
三、后端API的令牌验证配置
在Startup.cs中,确保正确配置Azure AD认证,验证前端传来的令牌:
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = $"https://login.microsoftonline.com/{_tenantId}/v2.0"; options.Audience = _clientId; // 后端API的应用ID options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = $"https://login.microsoftonline.com/{_tenantId}/v2.0" }; }); // 其他服务配置... } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // 其他中间件... app.UseAuthentication(); app.UseMvc(); }
四、为什么原来的方法不适用?
你参考的aspnetcore-connect示例是给服务器端应用设计的:用户通过后端的登录页面完成认证,后端会把用户的令牌缓存到Session中,所以cca.Users能找到用户。但你的SPA前端是在客户端完成登录,后端没有用户的登录会话,自然无法从缓存中找到用户数据,这就是问题的核心。
内容的提问来源于stack exchange,提问作者gboh
相关产品推荐
相关产品推荐

