On-Behalf-Of流调用AcquireTokenOnBehalfOf断言过期如何实现令牌刷新
问题根因
你当前的实现存在两个核心问题,直接导致id_token过期后无法自动刷新:
- 你将登录瞬间生成的
id_token作为固定值写入用户Claim持久化存储,该令牌默认有效期1小时,存储后不会自动更新,过期后作为OBO流的用户断言传入时,必然触发断言时间范围无效的错误。 - 你虽然已经通过
EnableTokenAcquisitionToCallDownstreamApi、AddInMemoryTokenCaches配置了微软身份类库内置的令牌缓存、自动刷新能力,但实际获取令牌时绕过了这套官方逻辑,手动实例化客户端、传入固定的过期id_token构造UserAssertion调用OBO接口,完全没有用到内置的刷新机制。
修复方案
第一步:清理Startup中冗余的令牌存储逻辑
你不需要手动把id_token存到Claim里,AddMicrosoftIdentityWebApp在开启SaveTokens = true后会自动管理用户的初始令牌、刷新令牌以及缓存,手动存固定值反而会拿到过期凭证。修改后的OnTicketReceived逻辑只保留业务必需的自定义Claim即可:
services.AddAuthentication("SomeCustomType") .AddMicrosoftIdentityWebApp(options => { Configuration.Bind("AzureAD", options); options.SaveTokens = true; options.Events.OnTicketReceived = async context => { var claimsIdentity = (ClaimsIdentity)context.Principal.Identity; claimsIdentity.AddClaim(new Claim("DirectAadAuth", "true")); // 移除原有手动存储userIdToken到Claim的逻辑 await Task.CompletedTask; }; }) .EnableTokenAcquisitionToCallDownstreamApi(Configuration.GetSection("CustomConfig").Get<CustomConfigModel>().DownstreamApiScopes) .AddInMemoryTokenCaches();
注意:你原有代码在
OnTicketReceived中二次调用SignInAsync切换到CustomType认证方案,如果不是业务强需求建议移除,多套认证方案会导致令牌上下文丢失;必须保留的话,后续获取令牌时要明确指定认证方案为CustomType。
第二步:改造TokenClient,使用官方令牌获取服务
不要手动创建UserAssertion、直接调用clientApplication.AcquireTokenOnBehalfOf,直接注入框架提供的ITokenAcquisition服务即可,它会自动处理令牌缓存、过期刷新、OBO断言有效性校验:当缓存中的令牌快过期时,会自动用刷新令牌换领新令牌,完全不需要手动处理id_token过期问题。
首先通过构造函数注入ITokenAcquisition:
private readonly ITokenAcquisition _tokenAcquisition; private readonly string[] _scopes; public TokenClient(ITokenAcquisition tokenAcquisition, IConfiguration configuration) { _tokenAcquisition = tokenAcquisition; _scopes = configuration.GetSection("CustomConfig").Get<CustomConfigModel>().DownstreamApiScopes; }
然后重写获取令牌的方法:
public async Task<string> GetAccessTokenAsync(ClaimsPrincipal user) { // 如果使用自定义认证方案,需要在参数中指定authenticationScheme: "CustomType" var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(_scopes, user: user); return accessToken; }
特殊场景兼容方案
如果因业务限制必须直接操作IConfidentialClientApplication手动调用AcquireTokenOnBehalfOf,需要做两个调整:
- 不要从Claim中读取固定存储的id_token,每次调用前通过
HttpContext.GetTokenAsync("id_token")从当前认证上下文读取最新的有效令牌构造UserAssertion - 调用接口时捕获
MsalUiRequiredException异常,当捕获到断言过期错误时,触发OpenID Connect的无感知刷新流程,不需要用户手动重新登录即可拿到新的有效令牌 - 如果是分布式部署场景,不要使用内存令牌缓存
AddInMemoryTokenCaches,替换为Redis、数据库等分布式缓存实现,避免应用重启、请求跨节点时缓存丢失触发强制登录。
内容的提问来源于stack exchange,提问作者kaigalmane
相关产品推荐
相关产品推荐

