Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/sharepoint/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为SharePoint Online O365构建多租户应用程序_Sharepoint_Azure_Oauth 2.0_Office365_Multi Tenant - Fatal编程技术网

为SharePoint Online O365构建多租户应用程序

为SharePoint Online O365构建多租户应用程序,sharepoint,azure,oauth-2.0,office365,multi-tenant,Sharepoint,Azure,Oauth 2.0,Office365,Multi Tenant,我正在尝试为Office 365构建一个多租户应用程序,该应用程序关注SharePoint Online,并使用OAuth2通过Azure进行身份验证。此问题特定于通过Azure登录访问SharePoint,但仅在使用此API使用OAuth2进行身份验证时才会发现 正确注册应用程序和在Azure和Office中设置用户的许多机制虽然有些复杂,但只要投入适当的时间就可以克服 即使是Azure的基本OAuth2协议使用也相对顺利。然而,由于SharePoint的“资源”参数,我的应用程序无法真正实现

我正在尝试为Office 365构建一个多租户应用程序,该应用程序关注SharePoint Online,并使用OAuth2通过Azure进行身份验证。此问题特定于通过Azure登录访问SharePoint,但仅在使用此API使用OAuth2进行身份验证时才会发现

正确注册应用程序和在Azure和Office中设置用户的许多机制虽然有些复杂,但只要投入适当的时间就可以克服

即使是Azure的基本OAuth2协议使用也相对顺利。然而,由于SharePoint的“资源”参数,我的应用程序无法真正实现多租户。这显然需要我的应用程序在最终用户完成登录序列之前知道其根SharePoint站点URL。我看不出这怎么可能。有人请给我指一下正确的方向

以下是实际登录顺序的示例:

GET /common/oauth2/authorize?client_id=5cb5e93b-57f5-4e09-97c5-e0d20661c59a
&redirect_uri=https://myappdomain.com/v1/oauth2_redirect/
&response_type=code&prompt=login&state=D79E5777 HTTP/1.1
Host: login.windows.net
Cache-Control: no-cache
当用户进行身份验证时,这将导致调用重定向,如下所示:

https://myappdomain.com/v1/oauth2_redirect/?code=AAABAAAAvPM1KaPlrEq...{blah*3} 
到目前为止棒极了!三条腿身份验证的下一步是回发到/token端点,以获取在所有后续REST调用中使用的实际承载令牌。这只是经典的OAuth2

POST /common/oauth2/token HTTP/1.1
Host: login.windows.net
Accept: text/json
Cache-Control: no-cache

----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="grant_type"

authorization_code
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="code"

AAABAAAAvPM1KaPlrEq...{blah*3}
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="client_id"

5cb5e93b-57f5-4e09-97c5-e0d20661c59a
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="client_secret"

02{my little secret}I=
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="redirect_uri"

https://myappdomain.com/v1/oauth2_redirect/
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="resource"

https://contoso.sharepoint.com/
----WebKitFormBoundaryE19zNvXGzXaLvS5C
这就是它变粘的地方。“resource”参数是必需的,并且必须指向您希望访问的特定于用户的端点。对于Exchange或Azure,终结点始终相同。(
https://graph.windows.net
https://outlook.office365.com
)但SharePoint对每个租赁都有不同的端点。您尚未实际登录该用户,但您已经需要尚未登录的有关该用户的信息

如果我部署的应用程序版本假定“contoso”为租户名称(如上所述),则只有contoso租户中的用户才能成功使用我的应用程序读取SharePoint数据。一旦fabrikam中的其他用户尝试使用它,我的
POST
/token
端点将请求对错误站点的权限。。。这就是问题所在

在用户实际登录之前,如何检测
POST
/token
端点的正确端点?是否有一些我可以使用的隐藏信息?是否存在某种可能的发现来检测租户的根SharePoint URL?或者更好的是,是否有一个端点可以作为资源传递,可以代表租户的家(类似
https://office.microsoft.com/sharepoint
)?然后可以从返回的用户id JWT令牌中潜在地收集它。这将类似于其他服务,并且对于客户机来说管理起来非常简单。然而,我看不到这一点


如果没有对这些问题的明确答案,或者没有解决这些问题的方法,我不得不猜测,不可能编写一个多租户应用程序来验证SharePoint Online O365。。。但这似乎并不正确。谁来帮忙

我想为我在上面的评论中简要提到的解决方案添加详细信息-这对于在Office 365中开发多租户应用程序的任何人都很重要,特别是如果该应用程序将访问SharePoint网站,包括OneDrive

从OAuth2.0的角度来看,这里的过程有点不标准,但在多租户世界中有一定意义。密钥正在重新使用Azure返回的第一个代码。请跟我来:

首先,我们遵循标准OAuth身份验证步骤:

GET /common/oauth2/authorize?client_id=5cb5e93b-57f5-4e09-97c5-e0d20661c59a
&redirect_uri=https://myappdomain.com/v1/oauth2_redirect/
&response_type=code&prompt=login&state=D79E5777 HTTP/1.1
Host: login.windows.net
Cache-Control: no-cache
这将重定向到用户登录的Azure登录页面。如果成功,Azure将使用以下代码回调您的端点:

https://myappdomain.com/v1/oauth2_redirect/?code=AAABAAAA...{ONE-CODE-To-RULE-THEM-ALL}xyz
现在,我们返回到
/token
端点,以获取在后续REST调用中使用的实际承载令牌。同样,这只是经典的OAuth2。。。但请注意我们如何使用
/Discovery
端点作为资源,而不是我们实际用于收集数据的任何端点。此外,我们要求提供
UserProfile.Read
scope

POST /common/oauth2/token HTTP/1.1
Host: login.windows.net
Accept: text/json
Cache-Control: no-cache

----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="grant_type"

authorization_code
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="code"

AAABAAAA...{ONE-CODE-To-RULE-THEM-ALL}xyz
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="client_id"

5cb5e93b-57f5-4e09-97c5-e0d20661c59a
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="client_secret"

02{my little secret}I=
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="redirect_uri"

https://myappdomain.com/v1/oauth2_redirect/
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="scope"

UserProfile.Read
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="resource"

https://api.office.com/discovery/
----WebKitFormBoundaryE19zNvXGzXaLvS5C
对此帖子的响应将包含一个
访问令牌
,可用于对
/discovery
端点进行REST调用

{
    "refresh-token":  "AAABsvRw-mAAWHr8XOY2lVOKZNLJ{BAR}xkSAA", 
    "resource": "https://api.office.com/discovery/", 
    "pwd_exp": "3062796", 
    "pwd_url": "https://portal.microsoftonline.com/ChangePassword.aspx", 
    "expires_in": "3599", 
    "access-token": "ey_0_J0eXAiOiJjsp6PpUhSjpXlm0{F00}-j0aLiFg", 
    "scope": "Contacts.Read", 
    "token-type": "Bearer", 
    "not_before": "1422385173", 
    "expires_on": "1422389073"
}
{
    "@odata.context": "https://api.office.com/discovery/v1.0/me/$metadata#allServices",
    "value": [
        {
            "capability": "MyFiles",
            "entityKey": "MyFiles@O365_SHAREPOINT",
            "providerId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
            "serviceEndpointUri": "https://contoso-my.sharepoint.com/_api/v1.0/me",
            "serviceId": "O365_SHAREPOINT",
            "serviceName": "Office 365 SharePoint",
            "serviceResourceId": "https://contoso-my.sharepoint.com/"
        },
        {
            "capability": "RootSite",
            "entityKey": "RootSite@O365_SHAREPOINT",
            "providerId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
            "serviceEndpointUri": "https://contoso.sharepoint.com/_api",
            "serviceId": "O365_SHAREPOINT",
            "serviceName": "Office 365 SharePoint",
            "serviceResourceId": "https://contoso.sharepoint.com/"
        },
        {
            "capability": "Contacts",
            "entityKey": "Contacts@O365_EXCHANGE",
            "providerId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
            "serviceEndpointUri": "https://outlook.office365.com/api/v1.0",
            "serviceId": "O365_EXCHANGE",
            "serviceName": "Office 365 Exchange",
            "serviceResourceId": "https://outlook.office365.com/"
        }
    ]
}
现在,使用此
访问令牌
,查询
/Services
端点,以了解Office 365中还有哪些可供此用户使用的内容

GET /discovery/v1.0/me/services HTTP/1.1
Host: api.office.com
Cache-Control: no-cache

----WebKitFormBoundaryE19zNvXGzXaLvS5D
Content-Disposition: form-data; name="Authorization"

Bearer ey_0_J0eXAiOiJjsp6PpUhSjpXlm0{F00}-j0aLiFg
----WebKitFormBoundaryE19zNvXGzXaLvS5D
结果将包括一系列服务结构,描述各个端点和每个端点的功能

{
    "refresh-token":  "AAABsvRw-mAAWHr8XOY2lVOKZNLJ{BAR}xkSAA", 
    "resource": "https://api.office.com/discovery/", 
    "pwd_exp": "3062796", 
    "pwd_url": "https://portal.microsoftonline.com/ChangePassword.aspx", 
    "expires_in": "3599", 
    "access-token": "ey_0_J0eXAiOiJjsp6PpUhSjpXlm0{F00}-j0aLiFg", 
    "scope": "Contacts.Read", 
    "token-type": "Bearer", 
    "not_before": "1422385173", 
    "expires_on": "1422389073"
}
{
    "@odata.context": "https://api.office.com/discovery/v1.0/me/$metadata#allServices",
    "value": [
        {
            "capability": "MyFiles",
            "entityKey": "MyFiles@O365_SHAREPOINT",
            "providerId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
            "serviceEndpointUri": "https://contoso-my.sharepoint.com/_api/v1.0/me",
            "serviceId": "O365_SHAREPOINT",
            "serviceName": "Office 365 SharePoint",
            "serviceResourceId": "https://contoso-my.sharepoint.com/"
        },
        {
            "capability": "RootSite",
            "entityKey": "RootSite@O365_SHAREPOINT",
            "providerId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
            "serviceEndpointUri": "https://contoso.sharepoint.com/_api",
            "serviceId": "O365_SHAREPOINT",
            "serviceName": "Office 365 SharePoint",
            "serviceResourceId": "https://contoso.sharepoint.com/"
        },
        {
            "capability": "Contacts",
            "entityKey": "Contacts@O365_EXCHANGE",
            "providerId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
            "serviceEndpointUri": "https://outlook.office365.com/api/v1.0",
            "serviceId": "O365_EXCHANGE",
            "serviceName": "Office 365 Exchange",
            "serviceResourceId": "https://outlook.office365.com/"
        }
    ]
}
现在是棘手的部分。。。在这一点上,我们知道我们真正想要验证的端点——其中一些是特定于租户的。通常,您会认为我们需要对每个端点重新播放OAuth2舞蹈。但是在这种情况下,我们可以使用上面相同的HTTP请求进行一些欺骗——只需发布我们最初从Azure收到的相同代码,只需使用上面服务结构中的serviceResourceId功能更改资源范围字段。像这样:

POST /common/oauth2/token HTTP/1.1
Host: login.windows.net
Accept: text/json
Cache-Control: no-cache

----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="grant_type"

authorization_code
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="code"

AAABAAAA...{ONE-CODE-To-RULE-THEM-ALL}xyz
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="client_id"

5cb5e93b-57f5-4e09-97c5-e0d20661c59a
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="client_secret"

02{my little secret}I=
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="redirect_uri"

https://myappdomain.com/v1/oauth2_redirect/
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="scope"

MyFiles.Read
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="resource"

https://contoso-my.sharepoint.com/
----WebKitFormBoundaryE19zNvXGzXaLvS5C
然后对其他两个执行相同的操作:

...
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="scope"

RootSite.Read
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="resource"

https://contoso.sharepoint.com/
----WebKitFormBoundaryE19zNvXGzXaLvS5C

所有这三个调用都将产生与上面第一篇文章类似的响应,为每个端点提供刷新令牌和访问令牌。所有这些都只需单用户身份验证的代价。:)


中提琴!谜团已解开-您可以为O365编写多租户应用程序。:)

啊,终于。经过数小时的研究、实验和开发,我终于找到了一个解决方案!这是Azure/O365 API的一个未记录的“功能”,因此我想确保每个对此主题感兴趣的人都能发现关键:显然,使用资源“”对Azure端点进行身份验证会产生一个代码,该代码可以在POST to/token步骤中多次使用-允许您对“discovery/v1.0/me/services”进行REST调用并迭代