我克隆了以下存储库active-directory-b2c-dotnetcore-webapp,并将其用作新应用程序的起点 . 一切都按预期工作,直到我在代码更改后重新编译代码 . 似乎每当我在正常开发过程中重新编译应用程序时,acquireTokenSilent函数都会返回会话过期消息 . 这显然是有问题的,因为它迫使我在每次代码更改后重新验证我的天蓝色租户 . 也许这与配置而不是azure b2c的.net核心缓存策略有关 . 这是startup.cs中的身份验证中间件:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAdB2C(options => Configuration.Bind("Authentication:AzureAdB2C", options))
.AddCookie();
// Add framework services.
services.AddMvc();
// Adds a default in-memory implementation of IDistributedCache.
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromHours(1);
options.CookieHttpOnly = true;
});
}
以及处理令牌缓存的类:
public class MSALSessionCache
{
private static ReaderWriterLockSlim SessionLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
string UserId = string.Empty;
string CacheId = string.Empty;
HttpContext httpContext = null;
TokenCache cache = new TokenCache();
public MSALSessionCache(string userId, HttpContext httpcontext)
{
// not object, we want the SUB
UserId = userId;
CacheId = UserId + "_TokenCache";
httpContext = httpcontext;
Load();
}
public TokenCache GetMsalCacheInstance()
{
cache.SetBeforeAccess(BeforeAccessNotification);
cache.SetAfterAccess(AfterAccessNotification);
Load();
return cache;
}
public void SaveUserStateValue(string state)
{
SessionLock.EnterWriteLock();
httpContext.Session.SetString(CacheId + "_state", state);
SessionLock.ExitWriteLock();
}
public string ReadUserStateValue()
{
string state = string.Empty;
SessionLock.EnterReadLock();
state = (string)httpContext.Session.GetString(CacheId + "_state");
SessionLock.ExitReadLock();
return state;
}
public void Load()
{
SessionLock.EnterReadLock();
cache.Deserialize(httpContext.Session.Get(CacheId));
SessionLock.ExitReadLock();
}
public void Persist()
{
SessionLock.EnterWriteLock();
// Optimistically set HasStateChanged to false. We need to do it early to avoid losing changes made by a concurrent thread.
cache.HasStateChanged = false;
// Reflect changes in the persistent store
httpContext.Session.Set(CacheId, cache.Serialize());
SessionLock.ExitWriteLock();
}
// Triggered right before MSAL needs to access the cache.
// Reload the cache from the persistent store in case it changed since the last access.
void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
Load();
}
// Triggered right after MSAL accessed the cache.
void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (cache.HasStateChanged)
{
Persist();
}
}
}
在以下代码块中调用acquireTokenSilentAsync之后,将引发会话到期 . 这只在重新编译应用程序后发生:
[Authorize]
public async Task<IActionResult> Api()
{
string responseString = "";
try
{
// Retrieve the token with the specified scopes
var scope = AzureAdB2COptions.ApiScopes.Split(' ');
string signedInUserID = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, this.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(AzureAdB2COptions.ClientId, AzureAdB2COptions.Authority, AzureAdB2COptions.RedirectUri, new ClientCredential(AzureAdB2COptions.ClientSecret), userTokenCache, null);
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scope, cca.Users.FirstOrDefault(), AzureAdB2COptions.Authority, false);
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, AzureAdB2COptions.ApiUrl);
// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);
// Handle the response
switch (response.StatusCode)
{
case HttpStatusCode.OK:
responseString = await response.Content.ReadAsStringAsync();
break;
case HttpStatusCode.Unauthorized:
responseString = $"Please sign in again. {response.ReasonPhrase}";
break;
default:
responseString = $"Error calling API. StatusCode=${response.StatusCode}";
break;
}
}
catch (MsalUiRequiredException ex)
{
responseString = $"Session has expired. Please sign in again. {ex.Message}";
}
catch (Exception ex)
{
responseString = $"Error calling API: {ex.Message}";
}
ViewData["Payload"] = $"{responseString}";
return View();
}
有没有办法让这个会话在重新编译后保持不变?
2 回答
这与令牌缓存有关 .
The sample README notes
MSALSessionCache
是令牌缓存的示例实现 . 此示例实现将缓存数据保留在内存中 . 它不会在应用程序重新启动时保留缓存数据,也不会跨服务器场共享缓存数据(除非您的会话是粘滞的) .有关将缓存数据持久保存到分布式缓存的选项,请参阅here .
我发现许多与B2C认证相关的天蓝色样本对于常见用例来说都是不完整的 . 幸运的是我偶然发现了这个添加了Reauthenticate全局过滤器的repository . 我修改了过滤器以适合我的OpenIdAuthenticationScheme,如下所示:
现在重新编译我的应用程序后,应用程序只是重定向到租户,似乎刷新令牌 . 我希望我可以赞成原作者!谢谢你的贡献 .