本文介绍了构建多租户应用程序SharePoint Online的O365的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图构建Office 365多租户应用程序,它专注于SharePoint Online中使用的OAuth2通过Azure的验证。问题是特定于通过Azure的登录的SharePoint访问,但使用此API使用的OAuth2进行身份验证时,只发现。

许多正常注册应用程序和设置在Azure和办公室用户的力学,而有些复杂,是可以战胜的大约时间投资的右侧。

即使使用Azure的基本的OAuth2协议使用工作比较顺利。不过,我在挫败凭借SharePoint的'资源'参数使我的申请真正的多租户。这显然​​需要我的应用程序,以了解最终用户的根SharePoint网站的网址,他们完成登录序列之前。我看不出这是可能的。有人请点我在正确的方向。

下面是实际的登录序列的样本:

  GET /普通/的oauth2 /授权?CLIENT_ID = 5cb5e93b-57f5-4e09-97c5-e0d20661c59a
&安培; REDIRECT_URI = HTTPS://myappdomain.com/v1/oauth2_redirect/
&安培; RESPONSE_TYPE = code&放;提示=登录&放大器;状态= D79E5777 HTTP / 1.1
主持人:login.windows.net
缓存控制:无缓存

当用户进行身份验证,这会导致所提供的重定向,看起来像这样的电话:

  HTTPS://myappdomain.com/v1/oauth2_redirect/ code = AAABAAAAvPM1KaPlrEq ... {嗒嗒* 3}

大为止!在3条腿认证的下一步是POST回/令牌终端获取的实际承载令牌的所有后续REST调用中使用。这仅仅是经典的OAuth2 ...

  POST /普通/的oauth2 /令牌HTTP / 1.1
主持人:login.windows.net
接受:文/ JSON
缓存控制:无缓存---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =grant_typeauthorization_ code
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =codeAAABAAAAvPM1KaPlrEq ... {嗒嗒* 3}
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =CLIENT_ID5cb5e93b-57f5-4e09-97c5-e0d20661c59a
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =client_secret02 {我的小秘密} I =
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =REDIRECT_URIhttps://myappdomain.com/v1/oauth2_redirect/
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =资源https://contoso.sharepoint.com/
---- WebKitFormBoundaryE19zNvXGzXaLvS5C

和这里的地方它会粘。在'资源'参数是必需的,并且必须指向你想要访问用户特定端点。对于Exchange或天青,终点是永远不变的。 ( https://graph.windows.net https://outlook.office365.com ),但拥有的SharePoint不同的端点为每个租户。您还没有实际尚未登录的用户,但已经需要有关用户,你还没有信息。

如果我部署一个版本,我的应用程序,它假定CONTOSO作为承租人的名称(如上),那么只有在Contoso租赁用户将使用获得成功我的应用程序读取SharePoint数据。只要在Fabrikam中另一个用户试图使用它,我的 POST /令牌端点将要求允许错误的网站...并且还有擦。

我怎样才能检测到正确的端点 POST 用户实际上在登录前/令牌端点?是否有是给我,我可以使用一些隐藏的信息?是否有某种发现可能检测租户的根的SharePoint网址是什么?或者更好的是,有没有我可以通过为资源的端点,可以为租客的家的再presentative 的(类似 https://office.microsoft .COM /共享点)?然后,它可以从USER_ID JWT令牌返回来收集潜在的。这将是类似的其他服务,和相当简单的客户端来管理。我没有看到这一点,但是。

如果没有一个明确的回答这些问题,或解决方法对这些问题,我不得不猜测,这是不是可以编写成进行身份验证SharePoint Online的O365多租户应用程序...这只是看起来不正确。有人请帮助!


解决方案

我想补充细节,该解决方案还简要提及了在上面我的评论 - 这将是任何人在Office 365开发多租户应用程序很重要,尤其是当应用程序将永远访问SharePoint网站,包括OneDrive。

下面的步骤从OAuth 2.0用户的角度来看有点不标准,但在做多租户的世界有所了解。关键是第一个code从Azure中返回重新使用。在这里跟我来:

首先,我们按照标准的OAuth认证步骤:

  GET /普通/的oauth2 /授权?CLIENT_ID = 5cb5e93b-57f5-4e09-97c5-e0d20661c59a
&安培; REDIRECT_URI = HTTPS://myappdomain.com/v1/oauth2_redirect/
&安培; RESPONSE_TYPE = code&放;提示=登录&放大器;状态= D79E5777 HTTP / 1.1
主持人:login.windows.net
缓存控制:无缓存

这重定向到Azure的登录页面,用户登录如果成功的话,那么Azure的调用回用code您的端点:

<$p$p><$c$c>https://myappdomain.com/v1/oauth2_redirect/?$c$c=AAABAAAA...{ONE-$c$c-To-RULE-THEM-ALL}xyz

现在我们回发到 /令牌在以后的REST调用中使用终端来获取实际的承载令牌。同样,这仅仅是经典的OAuth2 ......但看我们如何使用 /发现端点作为资源 - 而不是任何我们真正使用收集数据的端点。此外,我们要求 UserProfile.Read 范围。

  POST /普通/的oauth2 /令牌HTTP / 1.1
主持人:login.windows.net
接受:文/ JSON
缓存控制:无缓存---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =grant_typeauthorization_ code
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =codeAAABAAAA ... {单code到规则了他们,ALL} XYZ
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =CLIENT_ID5cb5e93b-57f5-4e09-97c5-e0d20661c59a
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =client_secret02 {我的小秘密} I =
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =REDIRECT_URIhttps://myappdomain.com/v1/oauth2_redirect/
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =范围UserProfile.Read
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =资源https://api.office.com/discovery/
---- WebKitFormBoundaryE19zNvXGzXaLvS5C

这个主题的响应将包含一个访问令牌,可以用来做REST调用的 /发现端点。

  {
    刷新令牌:AAABsvRw-mAAWHr8XOY2lVOKZNLJ {} BAR xkSAA
    资源:https://api.office.com/discovery/
    pwd_exp:3062796,
    pwd_url:https://portal.microsoftonline.com/ChangePassword.aspx,
    expires_in:3599,
    访问令牌:{ey_0_J0eXAiOiJjsp6PpUhSjpXlm0 F00} -j0aLiFg
    范围:Contacts.Read
    令牌类型:旗手,
    not_before:1422385173,
    expires_on:1422389073
}

现在,用这种访问令牌,查询 /服务端点找出可还有什么Office 365的该用户。

  GET /discovery/v1.0/me/services HTTP / 1.1
主持人:api.office.com
缓存控制:无缓存---- WebKitFormBoundaryE19zNvXGzXaLvS5D
内容处置:表格数据; NAME =授权承载ey_0_J0eXAiOiJjsp6PpUhSjpXlm0 {} F00 -j0aLiFg
---- WebKitFormBoundaryE19zNvXGzXaLvS5D

结果将包含服务结构的阵列,描述每个端点的各个端点和功能

  {
    @ odata.context:https://api.office.com/discovery/v1.0/me/$metadata#allServices,
    值:
        {
            能力:MYFILES
            的EntityKey:MYFILES @ O365_SHAREPOINT
            providerId:72f988bf-86f1-41af-91ab-2d7cd011db47,
            serviceEndpointUri:https://contoso-my.sharepoint.com/_api/v1.0/me,
            服务ID:O365_SHAREPOINT,
            服务名:Office 365中的SharePoint
            serviceResourceId:https://contoso-my.sharepoint.com/
        },
        {
            能力:RootSite,
            的EntityKey:RootSite @ O365_SHAREPOINT
            providerId:72f988bf-86f1-41af-91ab-2d7cd011db47,
            serviceEndpointUri:https://contoso.sharepoint.com/_api,
            服务ID:O365_SHAREPOINT,
            服务名:Office 365中的SharePoint
            serviceResourceId:https://contoso.sharepoint.com/
        },
        {
            能力:联系人,
            的EntityKey:联系人@ O365_EXCHANGE
            providerId:72f988bf-86f1-41af-91ab-2d7cd011db47,
            serviceEndpointUri:https://outlook.office365.com/api/v1.0,
            服务ID:O365_EXCHANGE,
            服务名:Office 365的交换,
            serviceResourceId:https://outlook.office365.com/
        }
    ]
}

现在到了棘手的部分...在这一点上,我们知道,我们真的要验证到终点 - 其中一些是租户特定的。通常你会认为我们需要与这些端点一遍播放的OAuth2舞。但是,在这种情况下,我们可以欺骗一个小 - 简单地张贴,我们最初是从Azure中收到了同样的code - 采用上述相同的HTTP请求,只改变了资源和范围字段中的 serviceResourceId 能力从上述服务的结构。像这样的:

  POST /普通/的oauth2 /令牌HTTP / 1.1
主持人:login.windows.net
接受:文/ JSON
缓存控制:无缓存---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =grant_typeauthorization_ code
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =codeAAABAAAA ... {单code到规则了他们,ALL} XYZ
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =CLIENT_ID5cb5e93b-57f5-4e09-97c5-e0d20661c59a
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =client_secret02 {我的小秘密} I =
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =REDIRECT_URIhttps://myappdomain.com/v1/oauth2_redirect/
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =范围MyFiles.Read
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =资源https://contoso-my.sharepoint.com/
---- WebKitFormBoundaryE19zNvXGzXaLvS5C

然后做同样为另外两个:

  ...
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =范围RootSite.Read
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =资源https://contoso.sharepoint.com/
---- WebKitFormBoundaryE19zNvXGzXaLvS5C

  ...
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =范围Contacts.Read
---- WebKitFormBoundaryE19zNvXGzXaLvS5C
内容处置:表格数据; NAME =资源https://outlook.office365.com/
---- WebKitFormBoundaryE19zNvXGzXaLvS5C

这三个调用将导致类似上面的第一篇文章的响应,为您提供一个刷新令牌和一个访问令牌每个相应的端点。所有这一切都为仅单个用户认证的​​价格。 :)

中提琴!解开了谜底 - 你可以写多租户应用O365。 :)

I am attempting to build a multi-tenant application for Office 365 which focuses on SharePoint Online and authenticates through Azure using OAuth2. The problem is specific to SharePoint access via the Azure login, but is only found when using this API to authenticate using OAuth2.

Many of the mechanics of registering the application properly and setting up users in Azure and Office, while somewhat complex, are conquerable with the right about of time investment.

Even the basic OAuth2 protocol usage with Azure works relatively smoothly. However, I'm thwarted in making my application truly multi-tenant by virtue of SharePoint's 'resource' parameter. This apparently requires my application to know the end-user's root SharePoint site URL before they complete the login sequence. I can't see how that is possible. Someone please point me in the right direction.

Here is a sample of the actual login sequence:

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

When the user authenticates, this results in a call to the redirect provided that looks like this:

https://myappdomain.com/v1/oauth2_redirect/?code=AAABAAAAvPM1KaPlrEq...{blah*3}

Great so far! The next step in the 3-legged authentication is a POST back to the /token endpoint to acquire the actual Bearer token to be used in all subsequent REST calls. This is just classic 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

and here's where it gets sticky. The 'resource' parameter is required, and must point to the user-specific endpoint that you want access to. For Exchange or Azure, the endpoint is always the same. (https://graph.windows.net or https://outlook.office365.com) But SharePoint has a different endpoint for every tenancy. You haven't actually logged the user in yet, but already you need information about the user that you don't yet have..

If I deploy a version of my application that assumes 'contoso' as the tenant name (as above) then only users in the contoso tenancy will succeed using my app to read SharePoint data. As soon as another user in fabrikam tries to use it, my POST to the /token endpoint will ask for permission to the wrong site... and there's the rub.

How can I detect the correct endpoint to POST to the /token endpoint prior to the user actually logging in? Is there some hidden information that is given to me that I can use? Is there some kind of discovery possible to detect the tenant's root SharePoint URL? Or better yet, is there an endpoint that I can pass as the resource that can be representative of the tenant's home (something like https://office.microsoft.com/sharepoint)? Then it could be potentially gleaned from the user_id JWT token returned. This would be similar to the other services, and quite simple for a client to manage. I don't see this, however.

Without a definitive answer to these questions, or workaround to these issues, I have to surmise that it is not possible to write a multi-tenant application that Authenticates into SharePoint Online O365... and that just doesn't seem right. Somebody please help!

解决方案

I want to add detail to the solution mentioned briefly in my comment above - This will be important to anyone developing multi-tenant applications in Office 365, especially if the application will ever access SharePoint sites including OneDrive.

The procedure here is a little non-standard from the OAuth 2.0 perspective, but makes some sense in the multi-tenant world. The key is re-using the first CODE returned from Azure. Follow me here:

First we follow the standard OAuth authentication steps:

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

This redirects to the Azure login page where the user logs in. If successful, Azure then calls back to your endpoint with a code:

https://myappdomain.com/v1/oauth2_redirect/?code=AAABAAAA...{ONE-CODE-To-RULE-THEM-ALL}xyz

Now we POST back to the /token endpoint to acquire the actual Bearer token to be used in subsequent REST calls. Again, this is just classic OAuth2... but watch how we use the /Discovery endpoint as the resource - instead of any of the endpoints we will actually be using to gather data. Also, we ask for 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

The response to this POST will contain an access-token that can be used to make REST calls to the /discovery endpoint.

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

Now, using this access-token, query the /Services endpoint to find out what else is available in Office 365 for this user.

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

The result will include an array of Service structures, describing the various endpoints and capabilities of each endpoint.

{
    "@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/"
        }
    ]
}

Now comes the tricky part... At this point, we know the endpoints that we really want to authenticate to - some of which are tenant-specific. Normally you'd think we need to play the OAuth2 dance all over again with each of these endpoints. But in this case, we can cheat a little - and simply POST the same CODE that we originally received from Azure - using the same HTTP request above, only altering the resource and the scope fields using the serviceResourceId and capability from the Service structure above. Like this:

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

then do the same for the other two:

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

All three of these calls will result in a response like the first POST above, providing you with a refresh-token and an access-token for each of the respective endpoints. All of this for the price of only a single user authentication. :)

Viola! Mystery solved - you CAN write multi-tenant applications for O365. :)

这篇关于构建多租户应用程序SharePoint Online的O365的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-22 19:27