首页 文章

Azure B2C Net Core开发工作流程

提问于
浏览
0

我克隆了以下存储库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 回答

  • 0

    这与令牌缓存有关 .

    The sample README notes MSALSessionCache 是令牌缓存的示例实现 . 此示例实现将缓存数据保留在内存中 . 它不会在应用程序重新启动时保留缓存数据,也不会跨服务器场共享缓存数据(除非您的会话是粘滞的) .

    有关将缓存数据持久保存到分布式缓存的选项,请参阅here .

  • 2

    我发现许多与B2C认证相关的天蓝色样本对于常见用例来说都是不完整的 . 幸运的是我偶然发现了这个添加了Reauthenticate全局过滤器的repository . 我修改了过滤器以适合我的OpenIdAuthenticationScheme,如下所示:

    internal class ReauthenticationRequiredException : Exception
    {
    }
    internal class ReauthenticationRequiredFilter : IExceptionFilter
    {
        private readonly AzureAdB2COptions _options;
    
        public ReauthenticationRequiredFilter(IOptions<AzureAdB2COptions> options)
        {
            this._options = options.Value;
        }
    
        public void OnException(ExceptionContext context)
        {
            if (!context.ExceptionHandled && IsReauthenticationRequired(context.Exception))
            {
    
                context.Result = new ChallengeResult(
                    OpenIdConnectDefaults.AuthenticationScheme,
                    new AuthenticationProperties {RedirectUri = context.HttpContext.Request.Path});
    
    
                context.ExceptionHandled = true;
            }
        }
    
        private static bool IsReauthenticationRequired(Exception exception)
        {
            if (exception is ReauthenticationRequiredException)
            {
                return true;
            }
    
            if (exception.InnerException != null)
            {
                return IsReauthenticationRequired(exception.InnerException);
            }
    
            return false;
        }
    }
    

    现在重新编译我的应用程序后,应用程序只是重定向到租户,似乎刷新令牌 . 我希望我可以赞成原作者!谢谢你的贡献 .

相关问题