Microsoft.IdentityModel.Clients.ActiveDirectory.AdalSilentTokenAcquisitionException:无法以静默方式获取令牌。调用方法AcquireToken

时间:2017-02-16 22:58:07

标签: c# azure asp.net-mvc-5 azure-active-directory adal

我使用内置的“配置Azure AD身份验证”功能配置了我的MVC webapp,它为我提供了以下代码:

public async Task<string> GetTokenForApplication()
    {
        string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
        string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
        string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;

        // get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
        ClientCredential clientcred = new ClientCredential(clientId, appKey);
        // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
        AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
        AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
        return authenticationResult.AccessToken;
    }

继承模特

public class ADALTokenCache : TokenCache
{
    private ApplicationDbContext db = new ApplicationDbContext();
    private string userId;
    private UserTokenCache Cache;

    public ADALTokenCache(string signedInUserId)
    {
        // associate the cache to the current user of the web app
        userId = signedInUserId;
        this.AfterAccess = AfterAccessNotification;
        this.BeforeAccess = BeforeAccessNotification;
        this.BeforeWrite = BeforeWriteNotification;
        // look up the entry in the database
        Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
        // place the entry in memory
        this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits,"ADALCache"));
    }

    // clean up the database
    public override void Clear()
    {
        base.Clear();
        var cacheEntry = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
        db.UserTokenCacheList.Remove(cacheEntry);
        db.SaveChanges();
    }

    // Notification raised before ADAL accesses the cache.
    // This is your chance to update the in-memory copy from the DB, if the in-memory version is stale
    void BeforeAccessNotification(TokenCacheNotificationArgs args)
    {
        if (Cache == null)
        {
            // first time access
            Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
        }
        else
        { 
            // retrieve last write from the DB
            var status = from e in db.UserTokenCacheList
                         where (e.webUserUniqueId == userId)
            select new
            {
                LastWrite = e.LastWrite
            };

            // if the in-memory copy is older than the persistent copy
            if (status.First().LastWrite > Cache.LastWrite)
            {
                // read from from storage, update in-memory copy
                Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
            }
        }
        this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits, "ADALCache"));
    }

    // Notification raised after ADAL accessed the cache.
    // If the HasStateChanged flag is set, ADAL changed the content of the cache
    void AfterAccessNotification(TokenCacheNotificationArgs args)
    {
        // if state changed
        if (this.HasStateChanged)
        {
            Cache = new UserTokenCache
            {
                webUserUniqueId = userId,
                cacheBits = MachineKey.Protect(this.Serialize(), "ADALCache"),
                LastWrite = DateTime.Now
            };
            // update the DB and the lastwrite 
            db.Entry(Cache).State = Cache.UserTokenCacheId == 0 ? EntityState.Added : EntityState.Modified;
            db.SaveChanges();
            this.HasStateChanged = false;
        }
    }

    void BeforeWriteNotification(TokenCacheNotificationArgs args)
    {
        // if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry
    }

    public override void DeleteItem(TokenCacheItem item)
    {
        base.DeleteItem(item);
    }
}

的DbContext:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext()
        : base("AzureDatabase")
    {
    }

    public DbSet<UserTokenCache> UserTokenCacheList { get; set; }
}

public class UserTokenCache
{
    [Key]
    public int UserTokenCacheId { get; set; }
    public string webUserUniqueId { get; set; }
    public byte[] cacheBits { get; set; }
    public DateTime LastWrite { get; set; }
}

首先,所有令牌缓存都存储在由连接到名为“defaultconnection”的数据库的函数自动配置的本地数据库中。然而,当我试图发布我的网络应用程序时,我遇到了错误,我意识到这是因为所有令牌都存储在本地。所以我尝试将DBContext迁移到我的Azure数据库。我迁移后,我一直得到这个例外。有没有办法解决这个问题?

1 个答案:

答案 0 :(得分:0)

根据我的理解,我们应该在调用AcquireTokenSilentAsync方法之前执行获取令牌而不使用刷新令牌。

例如,我们可以在用户登录后获取授权码后获取令牌。以下是一段代码供您参考:

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        ClientId = SettingsHelper.ClientId,
        Authority = SettingsHelper.Authority,                

        Notifications = new OpenIdConnectAuthenticationNotifications()
        {
            //
            // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.

            AuthorizationCodeReceived = (context) =>
            {
                var code = context.Code;

                ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
                String UserObjectId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;

                AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(UserObjectId));

                authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, SettingsHelper.AADGraphResourceId);
                //authContext.TokenCache.Clear();
                return Task.FromResult(0);
            }
        }
    });

您可以参考此方案的完整代码示例here以使用AcquireTokenSilentAsync函数。