You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Microsoft Teams SSO个人标签页定期调用Graph API遇对象释放异常求助

解决Teams Blazor个人标签页后台定时调用Graph API的令牌刷新问题

问题根源

你推测的原因完全正确:TeamsUserCredential刷新令牌时依赖前端JS上下文(比如静默登录交互),当用户切换到其他标签页后,Blazor组件的JS对象被释放,此时尝试刷新令牌就会抛出Cannot access a disposed object. Object Name: JSObjectReference异常。即便配置了offline_access权限,当前基于组件的TeamsUserCredential也无法在无JS上下文的后台环境中完成令牌刷新。

可行解决方案

方案一:后端托管令牌,独立管理生命周期(推荐)

彻底脱离前端JS上下文,将令牌管理逻辑移到后端,后台任务直接调用API:

    1. 调整应用注册:确保启用授权码流(带PKCE),并已获得offline_access及目标Graph API的委托权限(管理员同意)。
    1. 前端登录后传递授权码到后端:在Blazor组件完成用户登录后,通过TeamsUserCredential获取授权码,将其发送到后端API接口。
    1. 后端交换并存储令牌:后端用授权码调用Microsoft令牌端点,获取访问令牌、刷新令牌,将令牌与用户ID关联后存储到安全介质(比如加密数据库)。
    1. 后台定时任务调用API:使用ASP.NET Core BackgroundService、Hangfire或Quartz.NET创建定时任务,直接从存储中取刷新令牌,刷新访问令牌后调用Graph API。

这种方式完全不受前端标签页状态影响,是后台定时调用最可靠的方案。

方案二:自定义TokenCredential,绕过前端JS依赖

自定义令牌凭证实现,直接用刷新令牌刷新访问令牌,无需依赖前端交互:

    1. 登录时获取并存储刷新令牌:在用户首次登录后,通过TeamsUserCredential获取授权码,后端交换得到刷新令牌并保存。
    1. 实现自定义TokenCredential:
public class CustomUserCredential : TokenCredential
{
    private readonly string _refreshToken;
    private readonly string _clientId;
    private readonly string _tenantId;
    private readonly string _clientSecret;
    private string _currentAccessToken;
    private DateTimeOffset _expiresAt;

    public CustomUserCredential(string refreshToken, string clientId, string tenantId, string clientSecret)
    {
        _refreshToken = refreshToken;
        _clientId = clientId;
        _tenantId = tenantId;
        _clientSecret = clientSecret;
    }

    public override async Task<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
    {
        if (string.IsNullOrEmpty(_currentAccessToken) || DateTimeOffset.UtcNow >= _expiresAt.AddMinutes(-5))
        {
            var client = new HttpClient();
            var tokenRequest = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{_tenantId}/oauth2/v2.0/token")
            {
                Content = new FormUrlEncodedContent(new Dictionary<string, string>
                {
                    ["grant_type"] = "refresh_token",
                    ["client_id"] = _clientId,
                    ["refresh_token"] = _refreshToken,
                    ["scope"] = "https://graph.microsoft.com/.default offline_access",
                    ["client_secret"] = _clientSecret
                })
            };

            var response = await client.SendAsync(tokenRequest, cancellationToken);
            response.EnsureSuccessStatusCode();
            var tokenData = await response.Content.ReadFromJsonAsync<TokenResponse>();

            _currentAccessToken = tokenData.access_token;
            _expiresAt = DateTimeOffset.UtcNow.AddSeconds(tokenData.expires_in);
            // 可选:更新存储中的刷新令牌(如果返回了新的刷新令牌)
            if (!string.IsNullOrEmpty(tokenData.refresh_token))
            {
                _refreshToken = tokenData.refresh_token;
                // 同步更新数据库中的刷新令牌
            }
        }

        return new AccessToken(_currentAccessToken, _expiresAt);
    }

    public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
    {
        return new ValueTask<AccessToken>(GetTokenAsync(requestContext, cancellationToken));
    }

    private class TokenResponse
    {
        public string access_token { get; set; }
        public int expires_in { get; set; }
        public string refresh_token { get; set; }
    }
}
    1. 后台任务使用自定义凭证:初始化CustomUserCredential后,创建GraphServiceClient调用API,全程无需依赖前端组件。

方案三:基于Teams Bot实现后台调用(场景适配)

如果你的应用可以扩展为Teams Bot,Bot天然支持后台运行:

  • 创建Teams Bot并配置OAuth连接,引导用户完成授权后,Bot会获取到用户的刷新令牌并存储。
  • 使用Bot的后台服务(比如BackgroundService)定时调用Graph API,令牌刷新逻辑由Bot框架自动处理,完全脱离前端上下文。

内容的提问来源于stack exchange,提问作者Bilal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 01:52:44