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

SharePoint CSOM应用权限超时:运行1小时后触发401未授权错误

分析与解决方案:CSOM应用运行1小时后401未授权错误

我来帮你分析下这个运行1小时后出现的401未授权错误,核心原因和解决方案如下:

核心问题:App Only权限令牌过期

SharePoint的App Only权限令牌默认有效期是1小时,你的应用运行1小时后,最初获取的令牌会失效,此时再用旧的ClientContext发起请求就会抛出401错误。而你当前的代码在每次循环列表时都会新建ClientContext,每个上下文的令牌都会在1小时后过期,当流程运行到1小时后的请求时,就会触发这个问题。

具体解决方案&优化点

1. 复用ClientContext并实现令牌自动刷新

不要在foreach循环里每次新建ClientContext,而是复用同一个上下文,并添加逻辑在令牌即将过期时自动刷新。

首先,修改你的上下文获取方法,添加令牌过期检查与刷新逻辑:

public static ClientContext GetOrRefreshClientContext(ClientContext existingCtx)
{
    // 如果上下文为空,或者令牌即将过期,重新生成上下文
    if (existingCtx == null || IsTokenExpired(existingCtx))
    {
        existingCtx?.Dispose();
        var newCtx = new AuthenticationManager().GetAppOnlyAuthenticatedContext(SiteUrl, ClientId, ClientSecret);
        newCtx.RequestTimeout = 6000000;
        newCtx.ExecutingWebRequest += delegate (object sender, WebRequestEventArgs e) {
            e.WebRequestExecutor.WebRequest.UserAgent = "NONISV|Contoso|GovernanceCheck/1.0";
        };
        return newCtx;
    }
    return existingCtx;
}

// 辅助方法:检查令牌是否即将过期(提前5分钟刷新)
private static bool IsTokenExpired(ClientContext ctx)
{
    try
    {
        var executor = new WebRequestExecutor(ctx, new Uri(SiteUrl));
        executor.PrepareRequest();
        var authHeader = executor.WebRequest.Headers["Authorization"];
        if (string.IsNullOrEmpty(authHeader)) return true;
        
        var token = authHeader.Replace("Bearer ", "");
        var handler = new JwtSecurityTokenHandler();
        var jwtToken = handler.ReadJwtToken(token);
        // 提前5分钟刷新,避免刚好在请求时过期
        return jwtToken.ValidTo <= DateTime.UtcNow.AddMinutes(5);
    }
    catch
    {
        // 解析失败时默认认为令牌过期
        return true;
    }
}

然后在ProcessWebnLists方法中复用上下文:

static StringBuilder excelData = new StringBuilder();
static void ProcessWebnLists()
{
    ClientContext ctx = null;
    try
    {
        ctx = GetOrRefreshClientContext(null);
        string[] lists = { "ABC", "XYZ", "DEF", "GHI"};
        
        foreach (string strList in lists)
        {
            // 每次循环前检查并刷新上下文
            ctx = GetOrRefreshClientContext(ctx);
            if (ctx == null) 
            {
                WriteLog("Failed to get valid ClientContext for list: " + strList);
                continue;
            }
            
            Web web = ctx.Site.RootWeb;
            List list = web.Lists.GetByTitle(strList);
            
            // 批量加载需要的属性,减少HTTP请求次数
            ctx.Load(web);
            ctx.Load(list);
            ctx.ExecuteQuery();
            
            Console.WriteLine("List Name:{0}", list.Title);
            
            var query = new CamlQuery { ViewXml = "<View/>" };
            var items = list.GetItems(query);
            
            // 一次性加载所有需要的Item属性和权限信息,避免多次ExecuteQuery
            ctx.Load(items, 
                itemColl => itemColl.Include(
                    item => item.DisplayName,
                    item => item.RoleAssignments.Include(
                        ra => ra.Member.Title
                    )
                )
            );
            ctx.ExecuteQuery();
            
            foreach (var item in items)
            {
                Console.WriteLine(item.DisplayName);
                string groupNames = "";
                
                foreach (var assignment in item.RoleAssignments)
                {
                    try
                    {
                        groupNames += "[" + assignment.Member.Title.Replace(",", " ") + "] ";
                        Console.WriteLine($"--> {assignment.Member.Title}");
                    }
                    catch (Exception e)
                    {
                        WriteLog($"Method-ProcessWebnLists, List-{list.Title}, Document Title - {item.DisplayName}, Error : {e.Message}");
                        WriteLog($"Method-ProcessWebnLists, StackTrace : {e.StackTrace}");
                    }
                }
                
                excelData.AppendFormat("{0},{1},ITEM,{2}", list.Title, item.DisplayName.Replace(",", " "), groupNames);
                excelData.AppendLine();
            }
            excelData.AppendLine();
        }
    }
    catch (Exception ex)
    {
        WriteLog($"Method-ProcessWebnLists, Error : {ex.Message}");
        WriteLog($"Method-ProcessWebnLists, StackTrace : {ex.StackTrace.ToString()}");
    }
    finally
    {
        // 确保上下文被正确释放
        ctx?.Dispose();
    }
}

2. 减少ExecuteQuery的调用次数

你的原代码中每个Item、每个RoleAssignment都单独调用ExecuteQuery,这会产生大量的HTTP请求,不仅拖慢速度,还容易触发SharePoint的请求阈值。上面的修改使用Include方法批量加载所有需要的属性,把多次请求合并成一次,大幅提升效率,同时减少出错概率。

3. 确认应用权限配置

确保你在SharePoint注册应用时,授予的是站点集级别(或更高)的权限,并且该权限已经被管理员批准。虽然这个问题主要由令牌过期导致,但权限范围不足也可能触发401错误,需要排查确认。

额外建议

  • 添加重试逻辑:如果遇到401错误,可以捕获异常并尝试重新获取上下文后重试请求,避免单次错误中断整个流程。
  • 监控令牌状态:可以在日志中记录令牌的过期时间,方便排查问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:17:47