如何从提供商托管的应用程序获取请求摘要值? [英] How to get request digest value from provider hosted app?

查看:19
本文介绍了如何从提供商托管的应用程序获取请求摘要值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 javascript REST Api 开发 SharePoint 2013 Provider 托管应用程序.为了对共享点项目执行创建 (POST) 或更新 (MERGE) 操作,我需要使用请求设置X-RequestDigest"标头.

I am developing SharePoint 2013 Provider hosted app using javascript REST Api. In order to perform create (POST), or update (MERGE) operations on sharepoint items I need to set the 'X-RequestDigest' header with the request.

在 SharePoint 托管的应用程序中,我能够使用 http://contoso.sharepoint.com/SharePointHostedApp/_api/contextinfo 服务来检索请求摘要值;但是,在提供商托管的应用程序中,我无法获得该值.

When in SharePoint-hosted apps I was able to use the http://contoso.sharepoint.com/SharePointHostedApp/_api/contextinfo service to retrieve the request digest value; however, I am having trouble getting that value when in a provider hosted app.

提供者托管应用程序的第一个区别是,现在我们需要进行跨域请求,因为我们不是在共享点站点中运行,而是在托管在不同服务器上的不同域中运行.要明确:而不是

The first difference of provider hosted app is that now we need to make a cross-domain request since we are not running in a sharepoint site, but in a different domain hosted on a different server. To be clear: instead of

$.ajax({
    url: appWebUrl + '/_api/contextinfo',
    method: "POST",
    headers: { "Accept": "application/json; odata=verbose" }
})

我假设我们需要使用 SP.RequestExecutor 来执行跨域请求.当我构造请求时,它看起来如下所示(我已将实际 url 更改为假的,但基本上我们告诉代理使用主机 web 具有目标并获取 /_api/contextinfo端点):

I assumed we need to use the SP.RequestExecutor to execute a cross domain request. When I construct the request it looks like the following (I've changed the actual urls to something fake, but basically we're telling the proxy to use the host web has the target and get the /_api/contextinfo endpoint):

https://contoso-6f921c6addc19f.sharepoint.com/ProviderHostedApp/_api/SP.AppContextSite(@target)/contextinfo?@target=%27https://contoso.sharepoint.com%27

但是,我收到此错误:Cannot find resource for the request contextinfo. 意味着端点不存在.

However, I receive this error: Cannot find resource for the request contextinfo. meaning that the endpoint does not exist.

我确保将 POST 方法与正确的 application/json;odata=verbose 标头和空体一起使用.

I made sure to use the POST method with the correct application/json;odata=verbose headers with an empty body.

如何从 /_api/contextinfo 服务获取请求摘要值到提供者托管的应用程序?

How do I get the request digest value from the /_api/contextinfo service to the provider hosted app?

根据我的研究:

  • 我们不能使用 $('#__REQUESTDIGEST').val();因为这对提供商托管的应用程序不可用.
  • 我们需要使用一些来自跨域的请求,因为我在 sharepoint 之外运行.
  • 我尝试将跨域请求的目标设置为 hostWebUrl 和 appWebUrl,但都给出了相同的错误.
  • We can't use $('#__REQUESTDIGEST').val(); because that is not available to a provider hosted app.
  • We need to use some from of cross-domain request since I'm running outside of sharepoint.
  • I have tried setting the target of the cross-domain request to both the hostWebUrl and the appWebUrl and both give the same error.

必须有某种方式来获取这个值,否则我们在使用 JavaScript 时将仅限于读取操作.有没有其他人使用 javascript 解决过这个问题?

There must be some way to get this value, otherwise we would only be limited to read operations when using JavaScript. Has anyone else solved this using javascript?

从技术上讲,我可以尝试在服务器上使用 CSOM 实现所需的服务,并使用 WebAPI 或 WCF 公开它们,但必须实现它似乎不合理.

Technically I could try to implement the needed services using the CSOM on server and exposing them using WebAPI or WCF but it seem unreasonable to have to implement that.

更新:

我继续尝试添加一个 WebAPI 控制器,该控制器公开检索请求摘要值的服务.这实际上确实检索了请求摘要值;但是,当尝试在未来调用的标题中使用它时,我收到错误:此页面的安全验证无效并且可能已损坏.请使用您的网络浏览器的后退按钮再次尝试您的操作." 我猜请求摘要值中有一些引用头信息,表明它是由服务器请求的;但是,未来使用它发出的请求来自浏览器,这种不匹配可能是无效的可接受原因.

I went ahead and tried adding a WebAPI controller which exposes a service that retrieves the request digest value. This actually does retrieve a request digest value; however, when attempting to use this in the header of future calls I receive the error: "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." I'm guessing that the request digest value has some referer header information in it which indicates it was requested by the server; however, the future requests made with it are from the browser, and this mismatch might be an acceptable reason for it be invalid.

关于尝试添加 webAPI 控制器的更多说明.我的代码基于此示例:http://code.msdn.microsoft.com/SharePoint-2013-Perform-335d925b 但将其转换为使用较新的 HttpClient.我重载了 Page_Load 方法,将 contextTokenString 存储在一个可由 WebAPI 控制器访问的变量中,然后在请求上下文信息时解析/使用它.

Few more notes on the attempt at adding the webAPI controller. I based my code off of this example: http://code.msdn.microsoft.com/SharePoint-2013-Perform-335d925b but converted it to use the newer HttpClient. I overloaded the Page_Load method, stored the contextTokenString in a variable that could be accessed by the WebAPI controller then parsed/used it when requesting the contextinfo.

有谁知道这是否是对该错误的正确诊断?请求摘要值中是否有一些编码会阻止它像我建议的那样被检索?

Does anyone know if this is a correct diagnosis of that error? Is there something encoded in the request digest value that would prevent it from be able to be retrieved like I suggested?

我还在 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

I have also opened a related question on the MSDN forums since I'm desperate to find an answer: 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 编写时,我开始怀疑提供者托管应用程序的可行性.

I find it very hard to believe this could be a limitation of provider hosted applications, but given all testing I've done, I'm starting to doubt the viability of provider-hosted applications when you want to write in javascript.

求助!

推荐答案

我意识到您已经在提供者托管的应用程序的上下文中回答了您自己的问题,但是对于像我这样需要访问 REST API 的开发人员一种不基于 .NET 框架的语言(并且不能将他们的项目编写为 Web 应用程序)我想进一步扩展这个主题.我最近的任务是编写一个需要此功能的 iPad 应用程序,并最终对以下内容进行了逆向工程:

I realize you've already answered your own question within the context of a provider-hosted app, but for developers like myself who need to access the REST API from a language not based in the .NET framework, (and who cannot write their project as a web app) I'd like to expand on the subject a bit more. I was tasked with writing an iPad app recently that required this functionality, and ended up reverse-engineering the following:

不会真正涵盖这一点,因为有很多 示例在线演示更多常用方法.Microsoft.SharePoint.Client 库在使用 SharePoint Online 时似乎大多使用基于声明的身份验证,令牌通过位于以下位置的端点请求:https://login.microsoftonline.com/RST2.srf

Not going to actually cover this, as there are plenty of examples online that demonstrate the more common methods. The Microsoft.SharePoint.Client libraries mostly seem to use claims-based authentication when working with SharePoint Online, with the token being requested through the endpoint found at: https://login.microsoftonline.com/RST2.srf

如果您觉得懒惰,您可以随时获取经过身份验证的 cookie,向目标网站的主页发出 GET 请求,并使用如下正则表达式:

If you're feeling lazy, you can always take your authenticated cookies, make a GET request to the homepage of the target web, and use a regular expression like:

/(]*?)name="?__REQUESTDIGEST"?(?:[^>]*?)/>)/i

从响应中抓取 HTML.从那里开始,只需为您的摘要提取 value 属性即可.

to scrape the HTML from the response. From there, it'd just be a matter of extracting the value attribute for your digest.

CSOM 库当前在获取用于 API 调用的请求摘要时使用 SOAP 端点.您可以通过向 $(SPWebUrl)/_vti_bin/sites.asmx 网络服务发出类似于以下内容的 SOAP 请求来执行相同的操作:

The CSOM libraries currently use a SOAP endpoint when acquiring the request digest it uses for its API calls. You can do the same by making a SOAP request to the $(SPWebUrl)/_vti_bin/sites.asmx web service similar to the following:

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>

成功执行后,响应正文将如下所示:

When executed successfully, the response body will look something like:

<?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 块中提取您的请求摘要.

At that point, you can just extract your request digest from the DigestValue block.

我所知道的最后一种方法是使用对 $(SPWebUrl)/_api/contextinfo 端点发出的 OData 请求:

The last approach I'm aware of uses an OData request made to the $(SPWebUrl)/_api/contextinfo endpoint:

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

{}

成功执行后,响应正文将如下所示:

When executed successfully, the response body will look like the following:

{
    "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 属性中提取请求摘要.

The request digest can then be extracted from the FormDigestValue property.

如果您使用 CSOM,则您具有处理此内置的功能.(也可能是 JSOM,除非它使用 __REQUESTDIGEST 输入)Microsoft.SharePoint.Client.ClientContext 在内部使用 SOAP 方法来管理其请求摘要,并通过其 GetFormDigestDirect 公开公开此功能代码>方法.

If you're using CSOM, you have functionality for dealing with this built-in. (probably JSOM, too, unless it uses the __REQUESTDIGEST input) Microsoft.SharePoint.Client.ClientContext uses the SOAP approach internally to manage its request digest and publicly exposes this functionality through its GetFormDigestDirect method.

ClientContext clientContext = new ClientContext(webUrl);
// ...
FormDigestInfo formDigest = clientContext.GetFormDigestDirect();

// X-RequestDigest header value
string headerValue = formDigest.DigestValue;

// Digest expiration
DateTime expirationDate = formDigest.Expiration;

使用说明:虽然 ClientContext 为其请求维护和重用缓存的表单摘要,但此方法不允许您访问该缓存的值.相反,此方法会在每次调用时请求一个全新的表单摘要,因此您需要设置自己的缓存机制,以便在多个请求中重复使用未过期的摘要.

Usage Notes: While ClientContext maintains and reuses a cached form digest for its requests, this method does not give you access to that cached value. Instead, this method requests a brand new form digest with each call, so you'll want to setup your own caching mechanism in order to re-use unexpired digests across multiple requests.

如果您使用 JSOM API 并且无权访问 __REQUESTDIGEST 输入值,则可以使用 ClientContext 的缓存摘要="https://gist.github.com/juntalis/4ed013b5b2b299b36f5f" rel="noreferrer">以下扩展.(感谢 bdimag 指出缓存)

If you're using the JSOM API and don't have access to a __REQUESTDIGEST input value, you can access the ClientContext's cached digest with the following extensions. (Thanks to bdimag for pointing out the cache)

假设您在 TimeoutSeconds 过去之前使用请求摘要,则发出的有效 REST 请求如下所示:

Assuming you use the request digest before the TimeoutSeconds have elapsed, a valid REST request made like the following:

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"
    }
}

应该会导致成功响应.如果您检查该响应的标头,您会发现如下内容:

should result in a successful response. If you inspect the headers of that response, you'll find something like:

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) 开始,来自原始摘要请求,但我尚未确认)

Extracting the X-RequestDigest response header will allow you to use it in a subsequent call. (I'm guessing that the timeout starts over from the time of your new response + $(TimeoutSeconds) from the original digest request, but I've yet to confirm)

不幸的是,X-RequestDigest 标头仅由实际需要请求摘要的 REST 请求返回.对于不需要请求摘要的请求,您将不会收到标头,例如:$(SPWebUrl)/_api/web/lists/getByTitle('MyList')/items.如果在原始摘要超时后发现自己需要新摘要,则需要向 $(SPWebUrl)/_vti_bin/sites.asmx 网络服务发出另一个请求.

Unfortunately, the X-RequestDigest header is only returned by REST requests that actually require a request digest. You will not receive the header for requests where a request digest is unrequired, such as: $(SPWebUrl)/_api/web/lists/getByTitle('MyList')/items. Should you find yourself needing a new digest after the original has timed out, you'll need to make another request to the $(SPWebUrl)/_vti_bin/sites.asmx web service.

请求失败时的一些示例响应:

A few example responses from when our requests fail:

以下响应来自对 $(SPWebUrl)/_api/contextinfo 端点的 REST 请求.(未指定身份验证 cookie)

The following response comes from a REST request made to the $(SPWebUrl)/_api/contextinfo endpoint. (no authentication cookies specified)

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 标头.不确定这是否可用,但值得一试):

Next, a response originating from a REST request made with an expired request digest (Note the X-RequestDigest header specified in the response.. Not sure if that's usable, but it's worth a shot):

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."
        }
    }
}

这篇关于如何从提供商托管的应用程序获取请求摘要值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆