我在创建一个自动创建事件接收器的 nodejs 应用程序时偶然发现了这个问题。我发现如果我使用 PnP Powershell 模块创建远程事件接收器,事件就会触发。如果我将 REST API 与不记名令牌一起使用,它将无法工作。所以我安装了 Fiddler Web Debugger 并检查了 PnP 模块在验证时所做的调用。
问题似乎是事件接收器端点只是一个很好的基于 SOAP 的 SharePoint API 的包装器,它要求您提供 cookie 和 X-RequestDigest 标头以及您的请求。(在我看来,SharePoint REST API 应该提供一个有意义的错误消息来指出这一点,但事实并非如此。相反,如果您使用不记名令牌执行 POST,并且之后执行 GET,它会返回 204 状态代码它看起来好像添加了远程事件接收器,但它永远不会触发您订阅的事件,如前所述。)
我的解决方案只需要 Azure AD 中特权用户的用户名/密码。您不需要创建 Azure AD 应用程序,因为您不需要持有者令牌。
1. 请求安全令牌
对于 SharePoint Online,我们必须从该端点获取安全令牌:https ://login.microsoftonline.com/rst2.srf 。稍后我们将用这个令牌交换我们需要创建一个有效的远程事件接收器的 cookie。
确保在此节点中输入正确的用户名和密码:
<wsse:UsernameToken wsu:Id="user">
<wsse:Username>{USERNAME GOES HERE}</wsse:Username>
<wsse:Password>{PASSWORD GOES HERE}</wsse:Password>
</wsse:UsernameToken>
另外,更新此节点中的时间戳(不确定是否可以删除此节点):
<wsu:Timestamp Id="Timestamp">
<wsu:Created>2019-05-24T09:10:17.3179897Z</wsu:Created>
<wsu:Expires>2019-05-25T09:10:17.3179897Z</wsu:Expires>
</wsu:Timestamp>
这是请求的样子:
POST https://login.microsoftonline.com/rst2.srf HTTP/1.1
Content-Type: application/soap+xml; charset=utf-8
Host: login.microsoftonline.com
Content-Length: 1869
Expect: 100-continue
<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://www.w3.org/2003/05/soap-envelope" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:wst="http://schemas.xmlsoap.org/ws/2005/02/trust">
<S:Header>
<wsa:Action S:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>
<wsa:To S:mustUnderstand="1">https://login.microsoftonline.com/rst2.srf</wsa:To>
<ps:AuthInfo xmlns:ps="http://schemas.microsoft.com/LiveID/SoapServices/v1" Id="PPAuthInfo">
<ps:BinaryVersion>5</ps:BinaryVersion>
<ps:HostingApp>Managed IDCRL</ps:HostingApp>
</ps:AuthInfo>
<wsse:Security>
<wsse:UsernameToken wsu:Id="user">
<wsse:Username>{USERNAME GOES HERE}</wsse:Username>
<wsse:Password>{PASSWORD GOES HERE}</wsse:Password>
</wsse:UsernameToken>
<wsu:Timestamp Id="Timestamp">
<wsu:Created>2019-05-24T09:10:17.3179897Z</wsu:Created>
<wsu:Expires>2019-05-25T09:10:17.3179897Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</S:Header>
<S:Body>
<wst:RequestSecurityToken xmlns:wst="http://schemas.xmlsoap.org/ws/2005/02/trust" Id="RST0">
<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>
<wsp:AppliesTo>
<wsa:EndpointReference>
<wsa:Address>sharepoint.com</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<wsp:PolicyReference URI="MBI"></wsp:PolicyReference>
</wst:RequestSecurityToken>
</S:Body>
</S:Envelope>
响应应该在这个节点中包含一个安全令牌(我已经更改了令牌。真正的令牌应该更长):
<wsse:BinarySecurityToken Id="Compact0"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">t=EwDoAk6hBwAUamW55wMmWmiTTNEIaEUGbezNi9M5dTTKDfrZBTzqBswF4EgpEiBQgG1f9isd0lT3KFDE8pHKDaW0pgjiIbhWejs5SYC&p=
</wsse:BinarySecurityToken>
以下是完整的回复:
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store
Pragma: no-cache
Content-Type: application/soap+xml; charset=utf-8
Expires: -1
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
x-ms-request-id: [REMOVED BY AUTHOR]
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
Set-Cookie: fpc=[REMOVED BY AUTHOR]; expires=Sun, 23-Jun-2019 09:10:15 GMT; path=/; secure; HttpOnly
Set-Cookie: x-ms-gateway-slice=prod; path=/; secure; HttpOnly
Set-Cookie: stsservicecookie=ests; path=/; secure; HttpOnly
Date: Fri, 24 May 2019 09:10:15 GMT
Content-Length: 3497
<?xml version="1.0" encoding="utf-8"?>
<S:Envelope
xmlns:wsa="http://www.w3.org/2005/08/addressing"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wst="http://schemas.xmlsoap.org/ws/2005/02/trust"
xmlns:S="http://www.w3.org/2003/05/soap-envelope">
<S:Header>
<wsa:Action S:mustUnderstand="1" wsu:Id="Action">http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue</wsa:Action>
<wsa:To S:mustUnderstand="1" wsu:Id="To">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>
<wsse:Security S:mustUnderstand="1">
<wsu:Timestamp wsu:Id="TS"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsu:Created>2019-05-24T09:10:15.869581Z</wsu:Created>
<wsu:Expires>2019-05-24T09:15:15.869581Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</S:Header>
<S:Body
xmlns:S="http://www.w3.org/2003/05/soap-envelope">
<wst:RequestSecurityTokenResponse
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wst="http://schemas.xmlsoap.org/ws/2005/02/trust">
<wst:TokenType>urn:passport:compact</wst:TokenType>
<wsp:AppliesTo>
<wsa:EndpointReference
xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>sharepoint.com</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<wst:Lifetime>
<wsu:Created>2019-05-24T09:10:15Z</wsu:Created>
<wsu:Expires>2019-05-24T17:10:15Z</wsu:Expires>
</wst:Lifetime>
<wst:RequestedSecurityToken>
<wsse:BinarySecurityToken Id="Compact0"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">t=EwDoAk6hBwAUamW55wMmWmiTTNEIaEUGbezNi9M5dTTKDfrZBTzqBswF4EgpEiBQgG1f9isd0lT3KFDE8pHKDaW0pgjiIbhWejs5SYC&p=
</wsse:BinarySecurityToken>
</wst:RequestedSecurityToken>
<wst:RequestedAttachedReference>
<wsse:SecurityTokenReference
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:Reference URI="[REMOVED BY AUTHOR]"></wsse:Reference>
</wsse:SecurityTokenReference>
</wst:RequestedAttachedReference>
<wst:RequestedUnattachedReference>
<wsse:SecurityTokenReference
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:Reference URI="[REMOVED BY AUTHOR]"></wsse:Reference>
</wsse:SecurityTokenReference>
</wst:RequestedUnattachedReference>
</wst:RequestSecurityTokenResponse>
</S:Body>
</S:Envelope>
2. 用安全令牌交换 cookie
这个请求要简单得多,因为不需要正文。只需将安全令牌添加到授权中,如下所示。
GET https://{YOUR TENANT}.sharepoint.com/_vti_bin/idcrl.svc/ HTTP/1.1
Authorization: BPOSIDCRL t=EwDoAk6hBwAUamW55wMmWmiTTNEIaEUGbezNi9M5dTTKDfrZBTzqBswF4EgpEiBQgG1f9isd0lT3KFDE8pHKDaW0pgjiIbhWejs5SYC&p=
X-IDCRL_ACCEPTED: t
Host: {YOUR TENANT}.sharepoint.com
响应应该给你一个 cookie(示例中的 cookie 无效。真正的 cookie 将包含更长的字符串)。SPOIDCRL=SOMETHING
从cookie中提取。
HTTP/1.1 200 OK
Cache-Control: private
P3P: CP="ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI"
Set-Cookie: SPOIDCRL=77u/PD94bWwgdmVyc2lvkE9PTwvU1A+; path=/; secure; HttpOnly
X-SharePointHealthScore: 2
X-AspNet-Version: 4.0.30319
SPRequestGuid: [REMOVED BY AUTHOR]
request-id: [REMOVED BY AUTHOR]
MS-CV: [REMOVED BY AUTHOR]
Strict-Transport-Security: max-age=31536000
X-FRAME-OPTIONS: SAMEORIGIN
SPRequestDuration: 319
SPIisLatency: 0
X-Powered-By: ASP.NET
MicrosoftSharePointTeamServices: 16.0.0.8908
X-Content-Type-Options: nosniff
X-MS-InvokeApp: 1; RequireReadOnly
X-MSEdge-Ref: Ref A: [REMOVED BY AUTHOR] Ref B: [REMOVED BY AUTHOR] Ref C: 2019-05-24T09:10:15Z
Date: Fri, 24 May 2019 09:10:15 GMT
Content-Length: 0
3. 获取摘要值作为 X-RequestDigest
我们还需要 X-RequestDigest 值和 cookie,以便对 SharePoint REST API 进行任何更改。要获得摘要值,我们将POST
要{tenant}.sharepoint.com/sites/{your site}/_api/contextinfo
. 从响应中我们必须抓住FormDigestValue
. 请记住从我们在步骤 2 中获得的响应中附加 cookie。
POST /sites/{YOUR SITE}/_api/contextinfo HTTP/1.1
Host: {TENANT}.sharepoint.com
Content-Type: application/json; charset=utf-8
Accept: application/json
OData-Version: 4.0
Cookie: SPOIDCRL=77u/PD94bWwgdmVyc2lvkE9PTwvU1A+
回复正文:
{
"@odata.context": "https://{tenant}.sharepoint.com/sites/{your site}/_api/$metadata#SP.ContextWebInformation",
"FormDigestTimeoutSeconds": 1800,
"FormDigestValue": "0xA36B0B40BAF03EC,24 May 2019 20:36:14 -0000",
"LibraryVersion": "16.0.8908.1212",
"SiteFullUrl": "https://{tenant}.sharepoint.com/sites/{your site}",
"SupportedSchemaVersions": [
"14.0.0.0",
"15.0.0.0"
],
"WebFullUrl": "https://{tenant}.sharepoint.com/sites/{your site}"
}
4.创建一个新的远程事件接收器
最后我们可以做我们想做的事了,让我们使用 SharePoint REST API 创建一个新的远程事件接收器。
POST /sites/{your site}/_api/web/lists/getbytitle('Documents')/eventreceivers HTTP/1.1
Host: {tenant}.sharepoint.com
Content-Type: application/json; charset=utf-8
Accept: application/json
OData-Version: 4.0
X-RequestDigest: 0xA36B0B40BAF03EC,24 May 2019 20:36:14 -0000
Cookie: SPOIDCRL=77u/PD94bWwgdmVyc2lvkE9PTwvU1A+
{
"ReceiverAssembly": null,
"ReceiverClass": null,
"ReceiverName": "TestRERItemDeleted",
"SequenceNumber": 10000,
"Synchronization": 2,
"EventType": 10003,
"ReceiverUrl": "https://your-endpoint-where-you-want-the-requests-to-go"
}
我应该指出,如果您需要更新或删除事件接收器,您需要首先以创建接收器的同一用户身份进行身份验证。例如,如果您尝试使用不记名令牌删除接收者,则会收到错误消息。您必须对 cookie 进行身份验证并将其与 x-requestdigest 一起使用。如果您只想获取接收者,则可以使用不记名令牌。