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

Graph API无法正确返回AAD用户最后活动时间的问题排查

问题分析与解决方案

核心问题定位

你遇到的signInActivity数据异常(字段时而缺失、历史登录日期变更),既有Beta版API本身的特性限制,也存在代码实现上的隐患:

1. Beta版API的特性限制

Microsoft Graph Beta版属于预览版本,本身不保证数据的稳定性与一致性:

  • signInActivity的数据源依赖后台同步,存在延迟或回溯补录的情况(比如你遇到的历史日期变更,大概率是后台同步了更早的未记录登录数据)
  • 该字段的同步周期非实时,存在缓存机制,多次查询可能返回不同状态的数据

2. 代码实现的问题

你的解析逻辑和返回值处理存在明显bug:

  • 直接从AdditionalData取signInActivity并转字符串解析,未处理字段不存在的情况,容易触发空引用异常
  • 方法末尾错误返回未赋值的_userList,而非实际填充数据的uList,导致结果为空
  • 解析JSON时未做空值判断,若字段格式变化会直接抛出异常

修复后的代码实现

优化解析逻辑并修复bug:

internal static async Task<List<Users>> GetUsers()
{
    List<Users> uList = new List<Users>();
    try
    {
        IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
           .Create(AppId)
           .WithTenantId(TenantId)
           .WithClientSecret(ClientSecret)
           .Build();

        ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);

        GraphServiceClient graphClient = new GraphServiceClient(authProvider);
        graphClient.BaseUrl = Constant.BaseUrl;

        var result = await graphClient.Users
            .Request()
            .Select("Id,Mail,signInActivity")
            .GetAsync();
            
        while (true)
        {
            foreach (var item in result.CurrentPage)
            {
                var user = new Users
                {
                    Id = item.Id,
                    EmailId = item.Mail,                            
                    AdditionalData = item.AdditionalData,
                    User = item
                };

                // 安全处理signInActivity字段
                if (item.AdditionalData.TryGetValue("signInActivity", out var signInActivityObj))
                {
                    string signInJson = signInActivityObj.ToString();
                    if (!string.IsNullOrEmpty(signInJson))
                    {
                        try
                        {
                            var parsedJson = JObject.Parse(signInJson);
                            user.LastLoginDate = parsedJson["lastSignInDateTime"]?.ToString();
                            user.lastNonInteractiveLogInDate = parsedJson["lastNonInteractiveSignInDateTime"]?.ToString();
                        }
                        catch (JsonException ex)
                        {
                            log.LogError($"解析signInActivity失败,用户ID:{item.Id},异常:{ex.ToString()}");
                        }
                    }
                }
                else
                {
                    log.LogWarning($"用户ID:{item.Id} 未返回signInActivity字段");
                }
                
                uList.Add(user);
            }

            if (result.NextPageRequest is null)
                break;

            result = await result
              .NextPageRequest
              .GetAsync()
              .ConfigureAwait(false);
        }
    }
    catch (Exception ex)
    {
        log.LogError(ex.ToString());
    }

    // 修复:返回实际填充的uList
    return uList;
}

static string[] ToStringArray(object arg)
{
    var collection = arg as System.Collections.IEnumerable;
    if (collection != null)
    {
        return collection
          .Cast<object>()
          .Select(x => x.ToString())
          .ToArray();
    }

    if (arg == null)
    {
        return new string[] { };
    }

    return new string[] { arg.ToString() };
}

额外建议

  • 切换稳定版API:若对数据一致性要求高,建议使用Graph API v1.0版本,该版本signInActivity字段的同步规则更稳定,需确保应用已授予AuditLog.Read.All权限
  • 权限校验:确认应用拥有User.Read.All和AuditLog.Read.All权限,权限不足会导致signInActivity字段不返回
  • 本地缓存:针对非实时场景,可本地缓存查询结果,减少API调用带来的不一致问题
  • 空值处理:业务逻辑中对LastLoginDate为空的情况做特殊标记(如“无登录记录”)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 03:07:07