我正在使用javascript REST Api开发SharePoint 2013 Provider托管应用。为了对sharepoint项执行创建(POST)或更新(MERGE)操作,我需要为请求设置'X-RequestDigest'标头。
在SharePoint托管的应用程序中,我能够使用http://contoso.sharepoint.com/SharePointHostedApp/_api/contextinfo服务来检索请求摘要值;但是,在提供商托管应用程序时,我无法获得该值。
提供商托管应用程序的第一个区别是,现在我们需要发出跨域请求,因为我们不是在sharepoint站点中运行,而是在不同服务器上托管的不同域中运行。要明确:而不是
$.ajax({
url: appWebUrl + '/_api/contextinfo',
method: "POST",
headers: { "Accept": "application/json; odata=verbose" }
})
我假设我们需要使用SP.RequestExecutor
来执行跨域请求。
当我构造请求时,它看起来如下(我已经将实际的URL更改为假的,但基本上我们告诉代理使用主机web有目标并获得/_api/contextinfo
端点):< / p>
https://contoso-6f921c6addc19f.sharepoint.com/ProviderHostedApp/_api/SP.AppContextSite(@target)/contextinfo?@target=%27https://contoso.sharepoint.com%27
但是,我收到此错误:Cannot find resource for the request contextinfo.
表示端点不存在。
我确保将POST方法与正确的application/json;odata=verbose
标题一起使用,并使用空体。
如何从/_api/contextinfo
服务获取请求摘要值到提供商托管的应用程序?
根据我研究的内容:
$('#__REQUESTDIGEST').val()
;因为提供商托管的应用程序无法使用它。 必须有某种方法来获取此值,否则我们将仅限于使用JavaScript时的读取操作。 还有其他人使用javascript解决了这个问题吗?
从技术上讲,我可以尝试使用服务器上的CSOM实现所需的服务,并使用WebAPI或WCF公开它们,但是必须实现它是不合理的。
更新
我继续尝试添加一个WebAPI控制器,该控制器公开一个检索请求摘要值的服务。这实际上确实检索了请求摘要值;但是,当尝试在未来调用的标头中使用它时,我收到错误:"The security validation for this page is invalid and might be corrupted. Please use your web browser's Back button to try your operation again."
我猜测请求摘要值中有一些引用标头信息,表明它是服务器请求的;但是,未来使用它发出的请求来自浏览器,这种不匹配可能是无效的原因。
有关尝试添加webAPI控制器的更多注释。 我根据此示例创建了代码:http://code.msdn.microsoft.com/SharePoint-2013-Perform-335d925b但将其转换为使用较新的HttpClient。 我重载了Page_Load方法,将contextTokenString存储在一个变量中,该变量可以由WebAPI控制器访问,然后在请求contextinfo时解析/使用它。
有谁知道这是否是对该错误的正确诊断?是否在请求摘要值中编码了某些内容,以防止它像我建议的那样被检索?
我已经在MSDN论坛上打开了一个相关的问题,因为我迫切希望找到答案: http://social.msdn.microsoft.com/Forums/sharepoint/en-US/f601fddd-3747-4152-b2d1-4e89f0a771c4/question-about-limitation-of-providerhosted-apps-is-it-possible-to-make-rest-calls-with-javascript?forum=sharepointdevelopmentprevious
我发现很难相信这可能是提供商托管应用程序的限制,但考虑到我已经完成的所有测试,当你想用javascript编写时,我开始怀疑提供者托管应用程序的可行性。
乞求帮助!
答案 0 :(得分:28)
我意识到你已经在提供商托管的应用程序的上下文中回答了你自己的问题,但对于像我这样的开发人员需要从不是基于.NET框架的语言访问REST API(以及谁不能把他们的项目写成一个网络应用程序)我想更多地扩展这个主题。我最近负责编写一个需要此功能的iPad应用程序,并最终对以下内容进行逆向工程:
不会真正涵盖这一点,因为有很多examples online证明common methods更多。在使用SharePoint Online时,Microsoft.SharePoint.Client
库似乎主要使用基于声明的身份验证,并通过以下位置找到令牌:https://login.microsoftonline.com/RST2.srf
如果您感到懒惰,可以随身携带经过身份验证的Cookie,向目标网站的主页发出GET请求,并使用以下正则表达式:
/(<input (?:[^>]*?)name="?__REQUESTDIGEST"?(?:[^>]*?)\/>)/i
从响应中删除HTML。从那里开始,只需提取摘要的value
属性即可。
CSOM库在获取用于其API调用的请求摘要时,当前使用SOAP端点。您可以通过向$(SPWebUrl)/_vti_bin/sites.asmx
Web服务发出SOAP请求来执行相同的操作,类似于以下内容:
POST $(SPWebUrl)/_vti_bin/sites.asmx HTTP/1.1
Content-Type: text/xml
SOAPAction: http://schemas.microsoft.com/sharepoint/soap/GetUpdatedFormDigestInformation
X-RequestForceAuthentication: true
Host: $(SPSiteHostname)
Expect: 100-continue
Accept-Encoding: gzip, deflate
Cookie: $(Authenticated Cookies - Either "FedAuth=...; rtFa=..." or "SPOIDCRL=...")
Content-Length: $(Whatever)
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetUpdatedFormDigestInformation xmlns="http://schemas.microsoft.com/sharepoint/soap/" />
</soap:Body>
</soap:Envelope>
成功执行后,响应正文将如下所示:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetUpdatedFormDigestInformationResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/">
<GetUpdatedFormDigestInformationResult>
<DigestValue>0x1122334455 ... FF,27 Jul 2015 03:06:54 -0000</DigestValue>
<TimeoutSeconds>1800</TimeoutSeconds>
<WebFullUrl>$(SPWebUrl)</WebFullUrl>
<LibraryVersion>16.0.3208.1222</LibraryVersion>
<SupportedSchemaVersions>14.0.0.0,15.0.0.0</SupportedSchemaVersions>
</GetUpdatedFormDigestInformationResult>
</GetUpdatedFormDigestInformationResponse>
</soap:Body>
</soap:Envelope>
此时,您只需从DigestValue
块中提取请求摘要。
我所知道的最后一种方法是使用对$(SPWebUrl)/_api/contextinfo
端点发出的OData请求:
POST $(SPWebUrl)/_api/contextinfo HTTP/1.1
Host: $(SPSiteHostname)
DataServiceVersion: 3.0
Accept: application/json; odata=nometadata
Content-Type: application/json; odata=verbose
Cookie: $(Authenticated Cookies)
Content-Length: 2
{}
成功执行后,响应正文如下所示:
{
"FormDigestTimeoutSeconds" : 1800,
"FormDigestValue" : "0x1122334455 ... FF,27 Jul 2015 03:06:54 -0000",
"LibraryVersion" : "16.0.4230.1217",
"SiteFullUrl" : "$(SPSiteUrl)",
"SupportedSchemaVersions" : ["14.0.0.0", "15.0.0.0"],
"WebFullUrl" : "$(SPWebUrl)"
}
然后可以从FormDigestValue
属性中提取请求摘要。
如果您正在使用CSOM,则具有处理此内置功能的功能。 (可能是JSOM,除非它使用__REQUESTDIGEST输入)Microsoft.SharePoint.Client.ClientContext
在内部使用SOAP方法来管理其请求摘要,并通过其GetFormDigestDirect
方法公开此功能。
ClientContext clientContext = new ClientContext(webUrl);
// ...
FormDigestInfo formDigest = clientContext.GetFormDigestDirect();
// X-RequestDigest header value
string headerValue = formDigest.DigestValue;
// Digest expiration
DateTime expirationDate = formDigest.Expiration;
使用备注:虽然ClientContext
维护并重复使用其请求的缓存表单摘要,但此方法不允许您访问该缓存值。相反,此方法会在每次调用时请求全新的表单摘要,因此您需要设置自己的缓存机制,以便在多个请求中重复使用未过期的摘要。
如果您使用的是JSOM API且无法访问__REQUESTDIGEST
输入值,则可以使用the following extensions访问ClientContext
的缓存摘要。 (感谢bdimag指出缓存)
假设您在TimeoutSeconds
过去之前使用了请求摘要,则会发出如下有效的REST请求:
POST $(SPWebUrl)/_api/web/lists/getByTitle('MyList')/getchanges HTTP/1.1
Host: $(SPSiteHostname)
DataServiceVersion: 3.0
Accept: application/json; odata=nometadata
Content-Type: application/json; odata=verbose
X-RequestDigest: $(Request Digest)
Cookie: $(Authenticated Cookies)
Content-Length: 140
{
"query" : {
"__metadata" : {
"type" : "SP.ChangeQuery"
},
"Add" : "True",
"Item" : "True",
"Update" : "True"
}
}
应该会得到成功的回复。如果您检查该响应的标题,您会发现类似的内容:
HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Content-Type: application/json;odata=fullmetadata;streaming=true;charset=utf-8
...
X-RequestDigest: 0xAABBCC...00,03 Sep 2014 18:09:34 -0000
...
提取X-RequestDigest
响应标题将允许您在后续调用中使用它。 (我猜测超时从你的新回复+ $(TimeoutSeconds)
开始,从原始摘要请求开始,但我还没有确认)
不幸的是,X-RequestDigest
标头仅由实际需要请求摘要的REST请求返回。您将不会收到请求摘要不需要的请求的标头,例如:$(SPWebUrl)/_api/web/lists/getByTitle('MyList')/items
。如果您在原件超时后发现自己需要新的摘要,则需要向$(SPWebUrl)/_vti_bin/sites.asmx
网络服务发出另一个请求。
我们的请求失败时的一些示例响应:
以下响应来自对$(SPWebUrl)/_api/contextinfo
端点发出的REST请求。 (未指定身份验证cookie)
HTTP/1.1 403 Forbidden
Cache-Control: private, max-age=0
Content-Type: application/json;odata=nometadata;charset=utf-8
...
Server: Microsoft-IIS/8.5
X-SharePointHealthScore: 0
X-Forms_Based_Auth_Required: $(SPRootSiteUrl)/_forms/default.aspx?ReturnUrl=/_layouts/15/error.aspx&Source=%2f_vti_bin%2fclient.svc%2fcontextinfo
X-Forms_Based_Auth_Return_Url: $(SPRootSiteUrl)/_layouts/15/error.aspx
X-MSDAVEXT_Error: 917656; Access+denied.+Before+opening+files+in+this+location%2c+you+must+first+browse+to+the+web+site+and+select+the+option+to+login+automatically.
DATASERVICEVERSION: 3.0
X-AspNet-Version: 4.0.30319
X-IDCRL_AUTH_PARAMS_V1: IDCRL Type="BPOSIDCRL", EndPoint="$(SiteRelativeUrl)/_vti_bin/idcrl.svc/", RootDomain="sharepoint.com", Policy="MBI"
...
Date: Wed, 12 Aug 2015 02:27:35 GMT
Content-Length: 201
{
"odata.error" : {
"code" : "-2147024891, System.UnauthorizedAccessException",
"message" : {
"lang" : "en-US",
"value" : "Access denied. You do not have permission to perform this action or access this resource."
}
}
}
接下来,响应来自使用过期请求摘要的REST请求的响应(请注意响应中指定的X-RequestDigest
标头。不确定它是否可用,但值得一试):
HTTP/1.1 403 FORBIDDEN
Cache-Control: private, max-age=0
Content-Type: application/json;odata=fullmetadata;charset=utf-8
...
Server: Microsoft-IIS/8.5
Set-Cookie: rtFa=$(RtfaAuthCookie)
Set-Cookie: FedAuth=$(FedAuth)
X-SharePointHealthScore: 0
X-RequestDigest: 0x19EFFF80617AB2E48B0A9FF0ABA1440B5301E7445F3859177771BF6A39C7E4A74643108D862505A2C99350B0EDB871EF3DDE960BB68060601268818027F04956,12 Aug 2015 02:39:22 -0000
DATASERVICEVERSION: 3.0
X-AspNet-Version: 4.0.30319
...
Date: Wed, 12 Aug 2015 02:39:22 GMT
Content-Length: 253
{
"odata.error" : {
"code" : "-2130575251, Microsoft.SharePoint.SPException",
"message" : {
"lang" : "en-US",
"value" : "The security validation for this page is invalid and might be corrupted. Please use your web browser's Back button to try your operation again."
}
}
}
答案 1 :(得分:1)
您记得在权限级别存在一个禁用_api
下的所有服务的检查_api /网络/列表 _api /搜索/查询?QUERYTEXT =”的SharePoint” _api / SP.UserProfiles.PeopleManager
启用确保
网站设置 - &gt;网站权限 - &gt;权限级别 - &gt; read-&gt;
集成客户端功能 使用远程接口
我找到了解决方案 https://letrasandnumeros.com/2017/02/28/unauthorizedaccessexception-sharepoint-_api/
答案 2 :(得分:0)
RequestExecutor实际上为您处理RequestDigest。你不必得到它。
如果出于某种原因,您仍想获取RequestDigest值,请尝试在不更改上下文站点的情况下进行调用。
答案 3 :(得分:0)
好的,我创建了一个新的提供程序托管应用程序来重新测试问题。
您可以在此处查看存储库:
https://github.com/mattmazzola/providerhosted_01
在比较了这个新应用程序和旧应用程序后,我意识到我对SP.RequestExecutor如何构建预期URL有误解。我认为需要使用SP.AppContextSite()端点。
我错误地使用类似于以下内容的网址构建对appWeb的请求:
正如您所看到的,@ target被设置为appWeb url,但实际上在使用RequestExecutor向appWeb发出请求时,您无需执行此操作。它只是appweburl +“/ _ api / contextinfo”。只有在对hostWeb上存在的资源请求时,才需要使用AppContextSite并设置@target。
您可以在链接的解决方案中查看完整代码以获取更多详细信息。我添加了解决方案的屏幕截图。