.NET Core 3.1 MVC重启后AcquireTokenSilent报无账号传入错误
问题现象
- 应用基础信息:基于.NET Core 3.1开发的MVC Web应用,使用Azure Active Directory完成用户身份认证,借助MSAL拉取用户手机号、出生日期等个人信息
- 异常表现:应用运行期间所有功能正常,当重启Web应用且用户未主动登出时,应用虽然仍可识别用户声明,但会抛出状态码0错误:
No account or login hint was passed to the AcquireTokenSilent call.;用户执行登出操作后问题即可恢复 - 复现范围:本地localhost调试环境、发布至Azure后重启应用服务场景下均可稳定复现
相关配置与业务代码
services.AddOptions(); JwtSecurityTokenHandler.DefaultMapInboundClaims = false; IdentityModelEventSource.ShowPII = true; services.AddMicrosoftIdentityWebAppAuthentication(Configuration) .EnableTokenAcquisitionToCallDownstreamApi(new string[] { Constants.ScopeUserRead }) .AddMicrosoftGraph(Configuration.GetSection("GraphBeta")) .AddDistributedTokenCaches(); services.AddDistributedMemoryCache(); services.AddGraphService(Configuration); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options => { options.TokenValidationParameters.RoleClaimType = "roles"; //options.TokenValidationParameters.RoleClaimType = "groups"; options.TokenValidationParameters.NameClaimType = "name"; //added to save token // options.SaveTokens = true; }); services.AddControllersWithViews(); services.AddTokenAcquisition(); services.AddHttpClient(); services.AddScoped<IIncidentRepository, IncidentRepository>(); services.AddSession(); // 业务调用代码 try { Incident incident = new Incident(); // fetch and set user msid using GraphServiceClient if (HttpContext.User.Identity.IsAuthenticated) { ViewData["User"] = HttpContext.User; var me = await _graphServiceClient.Me.Request().GetAsync(); // 该行抛出错误 ViewData["UserId"] = me.OnPremisesSamAccountName; HttpContext.Session.SetString("MSID", ViewData["UserId"].ToString()); //var accessToken = await HttpContext.GetTokenAsync("access_token"); //string idToken = await HttpContext.GetTokenAsync("id_token"); } return View(incident); } catch (Exception ex) { _logger.ForContext("ClassName", this.GetType().Name) .ForContext("MethodName", ControllerContext.ActionDescriptor.ActionName).Error("Error is create incident", ex.StackTrace); }
问题根因
- 当前代码使用
AddDistributedMemoryCache()作为MSAL令牌缓存存储,该缓存属于应用进程内内存级存储,应用重启时进程内存会被完全回收,之前缓存的用户访问令牌、刷新令牌、账号关联信息会全部丢失。 - 应用重启后,用户的身份认证Cookie存储在客户端浏览器中且未过期,因此服务端可以正常解析到用户声明,判定
HttpContext.User.Identity.IsAuthenticated为true,但MSAL令牌缓存中已经不存在对应用户的账号记录与有效令牌。 - 调用Graph接口
_graphServiceClient.Me.Request().GetAsync()时,底层会自动调用AcquireTokenSilent方法尝试静默获取访问令牌,由于缓存中无对应账号信息,调用时也未传入登录提示参数,因此抛出指定错误。 - 用户主动登出时,客户端存储的认证Cookie会被清除,下次访问会重新走完整的Azure AD认证授权流程,重新向令牌缓存写入账号信息与令牌,因此问题会暂时消失,直到下次应用重启。
修复方案
- 替换非持久化的内存令牌缓存:不要在生产场景使用
AddDistributedMemoryCache()存储MSAL令牌,根据部署场景选择持久化分布式缓存:- 单实例部署可选用文件系统缓存
- Azure部署场景推荐使用Redis缓存、SQL Server或Cosmos DB作为分布式缓存存储,保证应用重启、多实例横向扩展时令牌缓存不会丢失
- 补充MSAL异常拦截逻辑:在调用Graph接口的代码块中捕获
MsalUiRequiredException异常,当缓存无有效令牌时主动触发重新授权流程,示例代码如下:
try { Incident incident = new Incident(); if (HttpContext.User.Identity.IsAuthenticated) { ViewData["User"] = HttpContext.User; var me = await _graphServiceClient.Me.Request().GetAsync(); ViewData["UserId"] = me.OnPremisesSamAccountName; HttpContext.Session.SetString("MSID", ViewData["UserId"].ToString()); } return View(incident); } catch (MsalUiRequiredException) { // 令牌缓存失效,触发重新认证,认证完成后跳转回当前页面 var authProps = new AuthenticationProperties { RedirectUri = Url.Action("CreateIncident") }; HttpContext.Challenge(authProps, OpenIdConnectDefaults.AuthenticationScheme); return new EmptyResult(); } catch (Exception ex) { _logger.ForContext("ClassName", this.GetType().Name) .ForContext("MethodName", ControllerContext.ActionDescriptor.ActionName).Error("Error is create incident", ex.StackTrace); }
- 放开令牌持久化配置:在OpenIdConnect配置项中取消
options.SaveTokens = true;的注释,确保认证成功后获取的令牌会被正确写入配置的持久化缓存中。
内容的提问来源于stack exchange,提问作者Priya
相关产品推荐
相关产品推荐

