我想为上面评论中简要提到的解决方案添加详细信息 - 这对于在 Office 365 中开发多租户应用程序的任何人都非常重要,特别是如果该应用程序将访问包括 OneDrive 在内的 SharePoint 网站。
从 OAuth 2.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
对此帖子的回复将包含access-token
可用于对/discovery
端点。
{
"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"
}
现在,使用这个access-token
,查询/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
结果将包括一系列服务结构,描述各种端点和每个端点的功能。
{
"@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 舞蹈。但在这种情况下,我们可以稍微作弊 - 只需 POST 我们最初从 Azure 收到的相同代码 - 使用上面相同的 HTTP 请求,仅更改resource
和scope
字段使用serviceResourceId
and capability
从上面的服务结构来看。像这样:
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
and
...
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="scope"
Contacts.Read
----WebKitFormBoundaryE19zNvXGzXaLvS5C
Content-Disposition: form-data; name="resource"
https://outlook.office365.com/
----WebKitFormBoundaryE19zNvXGzXaLvS5C
所有这三个调用都会产生类似于上面第一个 POST 的响应,为每个相应端点提供刷新令牌和访问令牌。所有这一切的代价只是一次用户身份验证。 :)
中提琴!谜团已解开 - 您可以为 O365 编写多租户应用程序。 :)