SharePoint订阅版REST API添加列表项失败(403禁止访问)
问题场景
在SharePoint Subscription Edition环境中,针对名为test2的列表,通过C#代码调用REST API添加列表项时,代码可成功获取Form Digest和列表实体类型,但执行POST请求添加项时返回403 Forbidden错误。使用的farmadmin账户可通过站点网页正常添加列表项。
原代码
using Newtonsoft.Json; using System.Net; using System.Net.Http.Headers; using System.Security; using System.Text; namespace TestList { public partial class Form1 : System.Windows.Forms.Form { private static readonly string siteUrl = "http://serverhost:39629/"; private static readonly string listName = "test2"; private static readonly string username = "farmadmin"; private static readonly string password = "Password"; private static readonly string domain = "DOMAIN.LOCAL"; private readonly CookieContainer cookieContainer = new CookieContainer(); public Form1() { InitializeComponent(); // 仅测试/开发环境绕过SSL验证 ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; // 启用TLS协议 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; } private async void btnAdd_Click(object sender, EventArgs e) { string newItemTitle = $"Test Item from C# REST - {DateTime.Now:yyyyMMdd_HHmmss}"; MessageBox.Show($"Attempting to add item: {newItemTitle}", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); var securePassword = new SecureString(); foreach (char c in password) securePassword.AppendChar(c); securePassword.MakeReadOnly(); var credentials = new NetworkCredential(username, securePassword, domain); using (var handler = new HttpClientHandler { Credentials = credentials, UseDefaultCredentials = false }) using (var client = new HttpClient(handler)) { client.DefaultRequestHeaders.Accept.Clear(); var acceptMediaType = new MediaTypeWithQualityHeaderValue("application/json"); acceptMediaType.Parameters.Add(new NameValueHeaderValue("odata", "verbose")); client.DefaultRequestHeaders.Accept.Add(acceptMediaType); try { // 获取Form Digest MessageBox.Show("Retrieving Form Digest Value...", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); var digestUrl = $"{siteUrl}_api/contextinfo"; var digestRequestContent = new StringContent(string.Empty); var contentTypeHeaderForDigest = new MediaTypeHeaderValue("application/json"); contentTypeHeaderForDigest.Parameters.Add(new NameValueHeaderValue("odata", "verbose")); digestRequestContent.Headers.ContentType = contentTypeHeaderForDigest; var digestResponse = await client.PostAsync(digestUrl, digestRequestContent); digestResponse.EnsureSuccessStatusCode(); var digestResponseBody = await digestResponse.Content.ReadAsStringAsync(); dynamic digestData = JsonConvert.DeserializeObject(digestResponseBody); string formDigestValue = digestData.d.GetContextWebInformation.FormDigestValue; if (string.IsNullOrWhiteSpace(formDigestValue)) { throw new Exception("Failed to retrieve Form Digest Value. The response from _api/contextinfo was: " + digestResponseBody); } MessageBox.Show($"Form Digest Value retrieved successfully.", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); // 获取列表实体类型 MessageBox.Show($"Retrieving List Item Entity Type Name for list '{listName}'...", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); var listSchemaUrl = $"{siteUrl}_api/web/lists/getByTitle('{listName}')?$select=ListItemEntityTypeFullName"; var listSchemaResponse = await client.GetAsync(listSchemaUrl); listSchemaResponse.EnsureSuccessStatusCode(); var listSchemaResponseBody = await listSchemaResponse.Content.ReadAsStringAsync(); dynamic listSchemaData = JsonConvert.DeserializeObject(listSchemaResponseBody); string listItemEntityTypeFullName = listSchemaData.d.ListItemEntityTypeFullName; if (string.IsNullOrWhiteSpace(listItemEntityTypeFullName)) { throw new Exception($"Failed to retrieve ListItemEntityTypeFullName for list '{listName}'."); } MessageBox.Show($"ListItemEntityTypeFullName for '{listName}': {listItemEntityTypeFullName}", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); // 准备请求体 var itemPayload = new { __metadata = new { type = listItemEntityTypeFullName }, Title = newItemTitle }; var jsonPayload = JsonConvert.SerializeObject(itemPayload); MessageBox.Show($"Prepared Item Payload (JSON body): {jsonPayload}", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); // 执行添加请求 var addUrl = $"{siteUrl}_api/web/lists/getByTitle('{listName}')/items"; var addRequestContent = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); var contentTypeForAddContent = new MediaTypeHeaderValue("application/json"); contentTypeForAddContent.Parameters.Add(new NameValueHeaderValue("odata", "verbose")); addRequestContent.Headers.ContentType = contentTypeForAddContent; addRequestContent.Headers.Add("X-RequestDigest", formDigestValue); MessageBox.Show($"Attempting to add new item to list '{listName}' via REST API...", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); var addResponse = await client.PostAsync(addUrl, addRequestContent); addResponse.EnsureSuccessStatusCode(); MessageBox.Show($"Item '{newItemTitle}' added successfully to list '{listName}'!", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (HttpRequestException httpEx) { string errorMessage = $"HTTP Request Error: {httpEx.Message}"; if (httpEx.StatusCode.HasValue) { errorMessage += $"\nStatus Code: {(int)httpEx.StatusCode.Value} {httpEx.StatusCode.Value}"; } if (httpEx.InnerException != null) { errorMessage += $"\nInner Exception: {httpEx.InnerException.Message}"; } HttpResponseMessage responseMessage = null; if (httpEx.Data.Contains("HttpResponseMessage")) { responseMessage = httpEx.Data["HttpResponseMessage"] as HttpResponseMessage; } if (responseMessage != null) { try { string responseBody = await responseMessage.Content.ReadAsStringAsync(); errorMessage += $"\nResponse Body: {responseBody}"; } catch (Exception contentReadEx) { errorMessage += $"\nCould not read Response Body: {contentReadEx.Message}"; } } MessageBox.Show(errorMessage, "SharePoint REST API Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (Exception ex) { string errorMessage = $"An unexpected error occurred: {ex.Message}"; if (ex.InnerException != null) { errorMessage += $"\nInner Exception: {ex.InnerException.Message}"; } MessageBox.Show(errorMessage, "SharePoint REST API Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } } }
错误信息
--------------------------- SharePoint REST API Error --------------------------- HTTP Request Error: Response status code does not indicate success: 403 (FORBIDDEN). Status Code: 403 Forbidden --------------------------- OK ---------------------------
核心问题及修复方案
1. Cookie容器未绑定到请求处理程序
SharePoint的Form Digest令牌与会话Cookie绑定,若获取Digest和添加项的请求使用不同会话,会导致令牌验证失败返回403。原代码中定义了cookieContainer但未赋值给HttpClientHandler,导致两次请求会话不共享。
修复代码:
修改HttpClientHandler的初始化代码,添加Cookie容器绑定和预认证设置:
using (var handler = new HttpClientHandler { Credentials = credentials, UseDefaultCredentials = false, CookieContainer = cookieContainer, // 绑定Cookie容器 PreAuthenticate = true, // 启用NTLM预认证 AllowAutoRedirect = true // 允许自动跳转 })
2. 简化请求头设置(可选)
可以直接设置Content-Type为application/json;odata=verbose,避免拆分参数的冗余操作:
// 替换原Content-Type设置代码 addRequestContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata=verbose");
3. 验证权限细节(排查用)
- 确认
farmadmin账户拥有站点的编辑权限(网页能操作则已满足,但可检查是否是直接权限而非继承) - 检查Web应用程序是否启用了安全验证(默认启用,若禁用可能导致其他问题,但403通常与此无关)
- 确认站点未启用自定义权限限制(如IRM或列表级权限拦截)
修改后的完整关键代码片段
private async void btnAdd_Click(object sender, EventArgs e) { string newItemTitle = $"Test Item from C# REST - {DateTime.Now:yyyyMMdd_HHmmss}"; MessageBox.Show($"Attempting to add item: {newItemTitle}", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); var securePassword = new SecureString(); foreach (char c in password) securePassword.AppendChar(c); securePassword.MakeReadOnly(); var credentials = new NetworkCredential(username, securePassword, domain); // 修复后的HttpClientHandler配置 using (var handler = new HttpClientHandler { Credentials = credentials, UseDefaultCredentials = false, CookieContainer = cookieContainer, PreAuthenticate = true, AllowAutoRedirect = true }) using (var client = new HttpClient(handler)) { client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;odata=verbose")); try { // 获取Form Digest MessageBox.Show("Retrieving Form Digest Value...", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); var digestUrl = $"{siteUrl}_api/contextinfo"; var digestRequestContent = new StringContent(string.Empty); digestRequestContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata=verbose"); var digestResponse = await client.PostAsync(digestUrl, digestRequestContent); digestResponse.EnsureSuccessStatusCode(); var digestResponseBody = await digestResponse.Content.ReadAsStringAsync(); dynamic digestData = JsonConvert.DeserializeObject(digestResponseBody); string formDigestValue = digestData.d.GetContextWebInformation.FormDigestValue; if (string.IsNullOrWhiteSpace(formDigestValue)) { throw new Exception("Failed to retrieve Form Digest Value. The response from _api/contextinfo was: " + digestResponseBody); } MessageBox.Show($"Form Digest Value retrieved successfully.", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); // 获取列表实体类型 MessageBox.Show($"Retrieving List Item Entity Type Name for list '{listName}'...", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); var listSchemaUrl = $"{siteUrl}_api/web/lists/getByTitle('{listName}')?$select=ListItemEntityTypeFullName"; var listSchemaResponse = await client.GetAsync(listSchemaUrl); listSchemaResponse.EnsureSuccessStatusCode(); var listSchemaResponseBody = await listSchemaResponse.Content.ReadAsStringAsync(); dynamic listSchemaData = JsonConvert.DeserializeObject(listSchemaResponseBody); string listItemEntityTypeFullName = listSchemaData.d.ListItemEntityTypeFullName; if (string.IsNullOrWhiteSpace(listItemEntityTypeFullName)) { throw new Exception($"Failed to retrieve ListItemEntityTypeFullName for list '{listName}'."); } MessageBox.Show($"ListItemEntityTypeFullName for '{listName}': {listItemEntityTypeFullName}", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); // 准备请求体 var itemPayload = new { __metadata = new { type = listItemEntityTypeFullName }, Title = newItemTitle }; var jsonPayload = JsonConvert.SerializeObject(itemPayload); MessageBox.Show($"Prepared Item Payload (JSON body): {jsonPayload}", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); // 执行添加请求 var addUrl = $"{siteUrl}_api/web/lists/getByTitle('{listName}')/items"; var addRequestContent = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); addRequestContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata=verbose"); addRequestContent.Headers.Add("X-RequestDigest", formDigestValue); MessageBox.Show($"Attempting to add new item to list '{listName}' via REST API...", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); var addResponse = await client.PostAsync(addUrl, addRequestContent); addResponse.EnsureSuccessStatusCode(); MessageBox.Show($"Item '{newItemTitle}' added successfully to list '{listName}'!", "SharePoint REST API", MessageBoxButtons.OK, MessageBoxIcon.Information); } // 异常处理部分保持不变 catch (HttpRequestException httpEx) { // ...原异常处理代码 } catch (Exception ex) { // ...原异常处理代码 } } }
内容的提问来源于stack exchange,提问作者Noorul
相关产品推荐
相关产品推荐

