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
相关产品推荐
相关产品推荐

