2014-12-04 52 views
7

我正在尝试为Office 365构建一个多租户应用程序,该应用程序专注于SharePoint Online并使用OAuth2通过Azure进行身份验证。该问题特定于通过Azure登录进行SharePoint访问,但只有在使用此API使用OAuth2进行身份验证时才能找到。构建SharePoint Online的多租户应用程序O365

在Azure和Office中正确注册应用程序并在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端点的POST,以获取将在所有后续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.nethttps://outlook.office365.com)但是SharePoint对于每个租户都有不同的端点。您还没有实际登录用户,但已经需要有关用户的信息但您还没有。

如果我部署的应用程序版本假定为'contoso'作为租户名称(如上所述),那么只有contoso租户中的用户才能使用我的应用读取SharePoint数据。只要fabrikam中的另一个用户尝试使用它,我的POST/token端点就会要求获得对错误站点的许可......并且存在问题。

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

如果没有对这些问题的明确答案或解决这些问题的办法,我必须猜测,编写一个多租户应用程序无法在SharePoint Online O365中进行身份验证......并且这看起来似乎没有对。有人请帮忙!

+2

啊,最后。经过数小时的研究,实验和开发 - 我偶然发现了一个解决方案!这是Azure/O365 API的一个未记录的“特性”,所以我想确保每个对此主题感兴趣的人都能发现关键:显然通过资源“https://api.office”向Azure端点进行身份验证。com/discovery /“会生成一个可以在POST to/token步骤中多次使用的CODE - 允许您对”discovery/v1.0/me/services“进行REST调用并遍历结果,获取令牌每个人都有相同的代码值。不客气:) – 2015-01-26 20:13:45

+0

回顾:[http://yazezo.com/2013/10/how-to-setup-saas-cloud-multi-tenant.html](How设置一个SaaS云?多租户,CRM 2011,Outlook2010) – 2015-09-25 16:02:46

回答

5

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

从OAuth 2.0的角度来看,这里的程序有点不标准,但在多租户领域有一定意义。关键是重新使用从Azure返回的第一个CODE。在这里跟我来:

首先,我们按照标准的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的登录页面,在用户登录如果成功,天青然后调用回你的端点代码。 :

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

现在我们回发到/token终端获取的实际承载令牌在后续REST调用中使用。再次,这只是传统的OAuth2 ...但请注意我们如何将/Discovery端点用作资源 - 而不是我们实际用于收集数据的任何端点。另外,我们要求UserProfile.Read范围。

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 

这个主题的响应将包含可用于制造REST调用的/discovery端点的access-token。现在

{ 
    "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": "[email protected]_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": "[email protected]_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": "[email protected]_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收到的代码相同的代码 - 仅使用上述Service结构中的serviceResourceIdcapability来更改resourcescope字段。就像这样:

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 

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

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

https://outlook.office365.com/ 
----WebKitFormBoundaryE19zNvXGzXaLvS5C 

所有这三个调用将导致像第一篇文章的响应上面的,为您提供每个相应端点的刷新令牌和访问令牌。所有这些仅以单一用户身份验证的价格提供。 :)

中提琴!神秘解决了 - 您可以为O365编写多租户应用程序。 :)

+2

天才。谢谢!这是我遇到过的最可怕的验证过程。很高兴我发现了这个。 – Jandieg 2015-02-19 01:26:42

+1

“这是最可怕的验证过程I '曾经处理过 - “听到了!+1 – papercowboy 2015-04-20 09:10:38

+0

干得好!:) - 但要小心,这个CODE的工作时间可能会有一个时间限制。 – Nils 2016-05-18 09:26:31