MS Dynamics首次身份验证令牌过期后抛出对象已释放错误
问题触发原因
- ISecureClientSecret对象被提前释放:你怀疑的方向是对的,绝大多数
ISecureClientSecret的官方实现都继承了IDisposable接口,如果你初始化时传入的_secret是局部创建对象、或者在依赖注入容器中注册为非单例生命周期,上层逻辑或者容器会在首次使用完成后主动释放该对象。首次调用initClient时对象还未被释放,所以能正常拿到令牌,等到令牌过期再次调用时,静态变量clientSecret指向的对象已经被回收/释放,AcquireTokenAsync内部读取密钥内容时就会抛出对象已释放错误。 - ADAL库的已知兼容性问题:你当前用的
AuthenticationContext属于已经停止维护的ADAL(Azure Active Directory Authentication Library)库,旧版本的ADAL在同步调用异步方法、复用ClientCredential实例时,确实存在内部对象被提前回收的未公开bug,和你碰到的报错场景完全匹配。 - 同步工具类的隐式副作用:你用的
RunSync方法虽然是通用的异步转同步实现,但在某些.NET Framework版本下,会导致异步执行上下文被提前销毁,使得AcquireTokenAsync内部用到的上下文对象被标记为已释放。
对应解决方法
- 优先修复密钥释放问题:不要直接存储传入的
ISecureClientSecret引用,初始化时就把密钥内容读取出来存为静态SecureString变量,每次调用initClient生成新的ClientCredential时都用这个存储的密钥值重新创建,完全避免依赖外部传入的对象生命周期:
// 新增静态变量存储密钥内容 private static SecureString _cachedSecret; internal static void Init(string _clientId, ISecureClientSecret _secret, string _authority = null, string _resourceUrl = null) { clientId = _clientId; // 初始化时就把密钥内容缓存下来,不保留原_secret的引用 _cachedSecret = new SecureString(); // 此处调用ISecureClientSecret的实现方法取出密钥内容存入_cachedSecret,不同实现类取法可能有差异,以你用到的实现为准 // 示例:如果是微软官方的SecureClientSecret,可以通过反射读取内部值,或者直接改用字符串密钥构造 if (!string.IsNullOrWhiteSpace(_authority)) { authority = _authority; } if (!string.IsNullOrWhiteSpace(_resourceUrl)) { resourceUrl = _resourceUrl; } initClient(); } private static void initClient() { try { // 每次都用缓存的密钥新建ClientCredential,不依赖原ISecureClientSecret对象 var clientCredentials = new ClientCredential(clientId, _cachedSecret); var authContext = new AuthenticationContext(authority, false); var authResult = DynamicsLeadWorkflow.AsyncUtil.RunSync<AuthenticationResult>(() => authContext.AcquireTokenAsync(resourceUrl, clientCredentials)); // 剩余原有逻辑不变 if(authResult == null || String.IsNullOrWhiteSpace(authResult.AccessToken)) { throw new UnauthorizedAccessException("Couldn't acquire the token from Dynamics"); } // 建议HttpClient改成单例,不要每次都新建,避免端口占用问题 _client ??= new HttpClient(); _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken); _client.BaseAddress = new Uri(resourceUrl); clientCreated = DateTime.Now; } catch (Exception ex) { ERROR(ex); } }
- 长期方案建议替换认证库:把已经废弃的ADAL库替换为微软当前主推的MSAL(Microsoft.Identity.Client)库,MSAL原生支持令牌自动刷新、静态实例复用,不需要自己手动判断令牌过期重新调用接口,完全规避当前碰到的问题。
- 临时规避同步调用问题:如果暂时不想改库,可以把
initClient改成异步方法,直接awaitAcquireTokenAsync的返回结果,去掉RunSync的包装,也能解决上下文销毁导致的释放报错。
内容的提问来源于stack exchange,提问作者Slipoch
相关产品推荐
相关产品推荐

