调用ITokenAcquisition.GetAccessTokenForUserAsync遇系列异常求助
我在Web API的Startup.cs中编写了如下代码,运行时调用GetAccessTokenForUserAsync抛出NullReferenceException异常,请问遗漏了什么配置?
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => { options.Events = new JwtBearerEvents() { OnTokenValidated = async ctx => { var tokenAcquisition = ctx.HttpContext.RequestServices .GetRequiredService<ITokenAcquisition>(); var graphClient = new GraphServiceClient( new DelegateAuthenticationProvider(async (request) => { var token = await tokenAcquisition .GetAccessTokenForUserAsync(new[] { "User.Read", "User.ReadBasic.All", "GroupMember.Read.All" }, user: ctx.Principal); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); })); // 获取用户组信息 var groups = await graphClient.Me.CheckMemberGroups(new[] { Constants.GroupId }) .Request() .PostAsync(); } }; }); services .AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddMicrosoftIdentityWebApi(this.Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi() .AddMicrosoftGraph() .AddInMemoryTokenCaches();
首次异常:NullReferenceException
更新1:添加authenticationScheme参数后出现新异常
发现调用GetAccessTokenForUserAsync时缺少authenticationScheme: JwtBearerDefaults.AuthenticationScheme参数,添加后又触发异常提示:
IDW10104: Both client secret and client certificate cannot be null or whitespace, and only ONE must be included in the configuration of the web app when calling a web API. For instance, in the appsettings.json file.
疑问:我已经使用调用方的用户令牌了,为什么还需要客户端密钥或证书?能否不用?Web API已获得用户的委托权限,且收到的令牌来自已认证用户。
更新2:添加客户端密钥后仍有异常
添加客户端密钥后,依旧出现NullReferenceException,同时新增异常提示IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user
更新3:附上更新后的完整ConfigureServices代码
public void ConfigureServices(IServiceCollection services) { var principalProvider = new ClaimsPrincipalProvider(); services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => { options.Events = new JwtBearerEvents() { OnTokenValidated = async ctx => { try { var tokenAcquisition = ctx.HttpContext.RequestServices .GetRequiredService<ITokenAcquisition>(); var graphClient = new GraphServiceClient( new DelegateAuthenticationProvider(async (request) => { var token = await tokenAcquisition .GetAccessTokenForUserAsync(new[] { "User.Read", "User.ReadBasic.All", "GroupMember.Read.All" }, user: ctx.Principal, authenticationScheme: JwtBearerDefaults.AuthenticationScheme); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); })); var groups = await graphClient.Me.CheckMemberGroups( new[] { Constants.GroupId }) .Request() .PostAsync(); } catch (System.Exception ex) { } } }; }); services .AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddMicrosoftIdentityWebApi(this.Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi() .AddMicrosoftGraph() .AddInMemoryTokenCaches(); services.AddAuthorization(options => { var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme); defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser(); options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build(); }); }
希望能解决这些异常问题。
1. 修复首次NullReferenceException
你直接覆盖了options.Events,但AddMicrosoftIdentityWebApi已经默认配置了JwtBearer的事件逻辑,覆盖后会导致ITokenAcquisition所需的上下文信息丢失,引发空引用。
正确做法:不要直接替换整个Events对象,而是在原有事件逻辑基础上添加自定义代码:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => { // 保存原有OnTokenValidated委托 var existingOnTokenValidated = options.Events.OnTokenValidated; options.Events.OnTokenValidated = async ctx => { // 先执行框架原有校验逻辑 await existingOnTokenValidated(ctx); // 再执行你的自定义逻辑 var tokenAcquisition = ctx.HttpContext.RequestServices .GetRequiredService<ITokenAcquisition>(); var graphClient = new GraphServiceClient( new DelegateAuthenticationProvider(async (request) => { var token = await tokenAcquisition .GetAccessTokenForUserAsync(new[] { "User.Read", "User.ReadBasic.All", "GroupMember.Read.All" }, user: ctx.Principal, authenticationScheme: JwtBearerDefaults.AuthenticationScheme); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); })); var groups = await graphClient.Me.CheckMemberGroups(new[] { Constants.GroupId }) .Request() .PostAsync(); }; });
2. 解释IDW10104异常并处理
Web API调用下游API(如Microsoft Graph)时,必须使用**On-Behalf-Of(OBO)**流程。这个流程中,Web API需要用自身的客户端凭据(密钥或证书)向Azure AD证明身份,才能用收到的用户令牌换取下游API的访问令牌,因此客户端凭据是必须配置的,无法省略。
你需要在appsettings.json的AzureAd节点中添加客户端密钥:
"AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "你的域名", "TenantId": "你的租户ID", "ClientId": "你的WebAPI客户端ID", "ClientSecret": "你的WebAPI客户端密钥" // 新增该行 }
3. 解决IDW10502异常
这个异常表示需要用户交互式登录,但Web API是无界面服务,出现该问题通常是以下原因:
- 用户令牌的受众(
aud声明)不是你的Web API客户端ID - 用户令牌缺少委托权限对应的
scp声明 - Web API未正确配置Microsoft Graph的委托权限,或未授予管理员同意
- OBO流程的权限传递逻辑错误
检查与修复步骤:
- 确认Web API在Azure AD中已添加Microsoft Graph的委托权限(User.Read、GroupMember.Read.All等),并且已经授予管理员同意
- 确认调用Web API的客户端应用,已请求Web API的权限(或直接请求了Graph的权限并通过OBO传递)
- 避免手动实例化
GraphServiceClient,直接使用框架注入的实例(AddMicrosoftGraph()已完成注册):
var graphClient = ctx.HttpContext.RequestServices.GetRequiredService<GraphServiceClient>();
4. 最终优化后的ConfigureServices代码
public void ConfigureServices(IServiceCollection services) { services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi(options => { options.Scopes = new[] { "User.Read", "User.ReadBasic.All", "GroupMember.Read.All" }; }) .AddMicrosoftGraph(options => { options.Scopes = string.Join(" ", new[] { "User.Read", "User.ReadBasic.All", "GroupMember.Read.All" }); }) .AddInMemoryTokenCaches(); // 配置JwtBearer事件,保留原有逻辑 services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => { var originalOnTokenValidated = options.Events.OnTokenValidated; options.Events.OnTokenValidated = async ctx => { await originalOnTokenValidated(ctx); try { var graphClient = ctx.HttpContext.RequestServices.GetRequiredService<GraphServiceClient>(); var groups = await graphClient.Me.CheckMemberGroups(new[] { Constants.GroupId }) .Request() .PostAsync(); // 可将组校验结果添加到用户声明中 var claimsIdentity = ctx.Principal.Identity as ClaimsIdentity; claimsIdentity.AddClaim(new Claim("isMemberOfTargetGroup", groups.Contains(Constants.GroupId).ToString())); } catch (Exception ex) { // 不要吞掉异常,建议记录日志后抛出 // _logger.LogError(ex, "校验用户组身份时出错"); throw; } }; }); services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser() .Build(); }); }
内容的提问来源于stack exchange,提问作者SYL

