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

.NET Maui中GetAccountsAsync返回null的MSAL B2B认证问题求助

问题描述

我开发的.NET Maui应用采用MSAL B2B认证,用户名密码登录功能正常。但在应用启动时,我尝试检查缓存中是否存储了有效访问令牌,发现代码行var accounts = await _application.GetAccountsAsync();返回null,导致AcquireTokenSilent方法无法正常执行。_application.GetAccountsAsync().IsFaulted为false。以下是我的认证流程代码:

private static IPublicClientApplication _application;
private static AuthenticationResult _authenticationResult;

private static void BuildApplication()
{
    _application = PublicClientApplicationBuilder.Create(B2BConstants.ClientId)
        .WithTenantId(B2BConstants.TenantId)
        .WithAuthority(B2BConstants.Authority)
        .WithRedirectUri("http://localhost")
        .Build();
}


/// <summary>
/// MSAL Authenticate Silent with token cache.
/// </summary>
/// <returns>AccessToken as String</returns>
public static async Task<string> AuthenticateSilentAsync()
{
    if (_application == null) BuildApplication();
    var accounts = await _application.GetAccountsAsync();

    _authenticationResult = await _application.AcquireTokenSilent(B2BConstants.Scopes, accounts.FirstOrDefault())
        .ExecuteAsync();

    return _authenticationResult.AccessToken;
}

public static async Task<string> AuthenticateAsync(string username, string password)
{
    BuildApplication();
    _authenticationResult = null;

    try
    {
        return await AuthenticateSilentAsync();
        
    }
    catch (MsalUiRequiredException ex)
    {
        try
        {
            _authenticationResult = await _application.AcquireTokenByUsernamePassword(B2BConstants.Scopes, username, password)
                .WithTenantId(B2BConstants.TenantId)
                .ExecuteAsync();

            return _authenticationResult.AccessToken;
        }
        catch (MsalException msalex)
        {
            return null;
        }
    }
    catch (Exception ex)
    {
        return null;
    }
}

我尝试通过GetAccountsAsync获取账户以传入AcquireTokenSilent,但返回的账户始终为null。请问这是什么原因?是否需要手动将令牌写入缓存,还是会自动完成?有没有替代方案?


解决方案

原因分析

  1. 默认缓存仅存于内存:MSAL的默认令牌缓存是内存级别的,仅在当前应用进程存活期间有效。应用重启后,内存缓存被清空,自然获取不到之前的账户信息。
  2. 无持久化缓存配置:MSAL不会自动处理跨进程/重启的缓存持久化,需要手动实现缓存的序列化与本地存储绑定,才能在应用重启后恢复账户和令牌数据。

解决步骤

1. 实现持久化令牌缓存

针对.NET Maui,我们可以通过ITokenCacheSerializer相关事件,将缓存数据序列化到本地存储(比如Preferences或SecureStorage,敏感场景推荐后者)。

添加缓存序列化工具类:

public class TokenCachePersistence
{
    private const string CacheStorageKey = "MSAL_B2B_TokenCache";

    public static void AttachToClientApp(IPublicClientApplication app)
    {
        app.UserTokenCache.SetBeforeAccess(LoadCacheFromStorage);
        app.UserTokenCache.SetAfterAccess(SaveCacheToStorage);
    }

    private static void LoadCacheFromStorage(TokenCacheNotificationArgs args)
    {
        // 从本地存储读取缓存并反序列化
        var cachedData = SecureStorage.GetAsync(CacheStorageKey).Result;
        if (!string.IsNullOrEmpty(cachedData))
        {
            args.TokenCache.DeserializeMsalV3(Convert.FromBase64String(cachedData));
        }
    }

    private static void SaveCacheToStorage(TokenCacheNotificationArgs args)
    {
        // 缓存状态变更时,序列化并写入本地存储
        if (args.HasStateChanged)
        {
            var serializedCache = Convert.ToBase64String(args.TokenCache.SerializeMsalV3());
            SecureStorage.SetAsync(CacheStorageKey, serializedCache).Wait();
        }
    }
}

2. 修改应用初始化逻辑

在BuildApplication中绑定缓存持久化逻辑:

private static void BuildApplication()
{
    _application = PublicClientApplicationBuilder.Create(B2BConstants.ClientId)
        .WithTenantId(B2BConstants.TenantId)
        .WithAuthority(B2BConstants.Authority)
        .WithRedirectUri("http://localhost")
        .Build();
    
    // 绑定持久化缓存
    TokenCachePersistence.AttachToClientApp(_application);
}

3. 修复静默登录的空引用问题

在AuthenticateSilentAsync中先判断账户是否存在,避免空引用并正确触发交互式登录:

public static async Task<string> AuthenticateSilentAsync()
{
    if (_application == null) BuildApplication();
    var accounts = await _application.GetAccountsAsync();
    var targetAccount = accounts.FirstOrDefault();

    if (targetAccount == null)
    {
        // 无缓存账户,抛出异常触发用户名密码登录流程
        throw new MsalUiRequiredException(MsalError.NoAccountsFound);
    }

    _authenticationResult = await _application.AcquireTokenSilent(B2BConstants.Scopes, targetAccount)
        .ExecuteAsync();

    return _authenticationResult.AccessToken;
}

替代方案

如果暂时不想实现持久化缓存,可以在应用启动时读取本地存储的用户名(比如用Preferences保存上次登录的用户名),直接调用AcquireTokenByUsernamePassword完成登录。这种方式本质是重新获取令牌,效率不如缓存持久化,但能绕过GetAccountsAsync为空的问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 14:13:16