为什么在获取访问令牌时在 C# 中出现 AADSTS50196 请求循环?

Why do I get a AADSTS50196 request loop in C# when getting a access token?

我想通过 C# 向 Azure Active Directory 进行身份验证并从 SharePoint 检索大量数据。因此我添加了并行请求和中央身份验证方法。在方法中我缓存了令牌,所以我只需要请求一次令牌。

public class AuthHelper
{
    string authorityUrl = "https://login.microsoftonline.com/{0}/";
    public string TenantId { get; set; } = "<GUID>";
    public string ClientId { get; set; } = "<GUID>";
    public string Username { get; set; } = "MyUsername";
    public string UserPW { get; set; } = "MyPa$$w0rd!";
    public string HostURL { get; set; } = "http://contoso.sharepoint.com/...";

    private string tmpToken;
    public string GetJWTToken()
    {
        // get new Token from service
        if (IsTokenExpired(tmpToken))
        {
            var scopes = new List<string>();
            // parse uri, to use hostname like 'contoso.sharepoint.com' with the default rights
            var uri = new Uri(HostURL);
            var resource = uri.Scheme + Uri.SchemeDelimiter + uri.Host + "/.default";
            scopes.Add(resource);

            IPublicClientApplication app = PublicClientApplicationBuilder.Create(ClientId)
                .WithAuthority(string.Format(authorityUrl, TenantId))
                .WithTenantId(TenantId)
                .Build();

            var securePassword = new SecureString();
            foreach (char c in UserPW) securePassword.AppendChar(c);
            // following line throw the exception
            var result = app.AcquireTokenByUsernamePassword(scopes, Username, securePassword).ExecuteAsync().Result;
            tmpToken = result.AccessToken;
        }
        return tmpToken;
    }

    public bool IsTokenExpired(string token)
    {
        if (token == null)
            return true;
        // check if tmpToken is still valid
        else
        {
            var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
            var tokenDecoded = handler.ReadJwtToken(token);
            if (tokenDecoded.ValidTo < DateTime.UtcNow)
                return true;
        }
        return false;
    }
}

从 SharePoint 并行请求多个资源时,出现以下异常:

AADSTS50196: The server terminated an operation because it encountered a client request loop. Please contact your app vendor. Trace ID: <GUID> Correlation ID: <GUID> Timestamp: ...

我的问题是,我为每个并行 运行 线程使用了 class 的新实例。因此,每次请求访问令牌时,缓存总是新创建的。

根据 Microsoft 的说法,请求令牌存在限制,即使是成功的请求也是如此:

multiple requests (15+) in a short period of time (5 minutes) will receive an invalid_grant error

(来源:https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-breaking-changes#march-2019

现在我用 static List 的访问令牌实现了一个全局缓存。我根据 clientId 和用户名进行检查,但如果需要可以扩展。

已将 private string tmpToken; 替换为:

    protected class CustTokenCacheItem
    {
        public string ClientId { get; set; }
        public string Username { get; set; }
        public string AccessToken { get; set; }
    }

    private static readonly List<CustTokenCacheItem> TokenCache = new List<CustTokenCacheItem>();
    private string tmpToken
    {
        get
        {
            return TokenCache.FirstOrDefault(t => t.ClientId == this.ClientId && t.Username == this.Username)?.AccessToken;
        }
        set
        {
            var tkn = TokenCache.FirstOrDefault(t => t.ClientId == this.ClientId && t.Username == this.Username);
            if (value == null && tkn != null)
            {
                TokenCache.Remove(tkn);
            }
            else if (tkn == null)
            {
                TokenCache.Add(new CustTokenCacheItem { AccessToken = value, Username = this.Username, ClientId = this.ClientId });
            }
            else
            {
                tkn.AccessToken = value;
            }
        }
    }