回忆是一座桥
却是通往寂寞的牢

通过REST API读取SharePoint Online上的文件 - 服务主体模式

本篇文章将介绍以服务主体模式调用REST API来访问或处理SharePoint Online上的文件,主要用于没有SharePoint官方接口的应用程序或需要以编程方式访问SharePoint时的场景。

所谓的服务主体模式,即以应用自身的凭据来获取访问令牌,通常用于在后台运行而无需用户参与的守护程序。应用自身的凭据一般为应用的客户端密码,但若想通过Azure应用来获取调用SharePoint的REST API的访问令牌,则必须使用上传证书的方式来作为应用凭据,其它方式都已被屏蔽。但证书的生成与上传,以及后续的调用都很麻烦,因此将改用SharePoint应用的方式来实现服务主体模式,而不使用Azure应用,可以有效简化操作流程,但需要具有SharePoint租户管理员权限或联系SharePoint租户管理员打开相应功能。

本篇文章所给出的方案涉及到SharePoint租户属性的修改,因此建议先阅读本文末尾的附录所给出的信息,具备所有必要条件后,再回过头来阅读。

注册SharePoint应用

1、打开需要读取的文件所在的SharePoint站点

2、从浏览器上方地址栏处截取SharePoint站点的链接,格式为:

https://{tenant_name}.sharepoint.com/sites/{site_name}

3、在SharePoint站点的链接后拼接该路径:/_layouts/15/appregnew.aspx ,即可得到SharePoint应用的注册页面的链接,此时完整的链接应该为:

https://{tenant_name}.sharepoint.com/sites/{site_name}/_layouts/15/appregnew.aspx

4、在浏览器打开上述链接,进入SharePoint应用的注册页面,然后依次生成应用的客户端ID与密钥,并输入其它信息,然后点击创建,具体如下图所示:

生成客户端ID或密钥时,若遇到提示:“SharePoint租户管理员不允许网站集管理员创建Azure访问控制(ACS)主体。请联系您的SharePoint租户管理员”,则请参考本文末尾的附录所给出的信息。

5、记录SharePoint应用的客户端ID与客户端密钥,后续需要使用

为SharePoint应用授权

1、在SharePoint站点的链接后拼接该路径:/_layouts/15/appinv.aspx ,即可得到SharePoint应用的授权页面的链接,此时完整的链接应该为:

https://{tenant_name}.sharepoint.com/sites/{site_name}/_layouts/15/appinv.aspx

2、在浏览器打开上述链接,进入SharePoint应用的授权页面,如下图所示:

3、输入之前注册的SharePoint应用的客户端ID,然后点击查阅

4、根据自身情况选择授权的范围,然后将相应的XML内容填入权限请求XML位置,推荐使用网站集范围,然后点击创建:

安装外接程序的租户,包括此范围的所有子级:
<AppPermissionRequests AllowAppOnlyPolicy="true">
  <AppPermissionRequest Scope="http://sharepoint/content/tenant" Right="FullControl" />
</AppPermissionRequests>

安装外接程序的网站集,包括此范围的所有子级:
<AppPermissionRequests AllowAppOnlyPolicy="true">
  <AppPermissionRequest Scope="http://sharepoint/content/sitecollection" Right="FullControl" />
</AppPermissionRequests>

安装外接程序的网站,包括此范围的所有子级:
<AppPermissionRequests AllowAppOnlyPolicy="true">
  <AppPermissionRequest Scope="http://sharepoint/content/sitecollection/web" Right="FullControl" />
</AppPermissionRequests>

以上Right可选的值有:FullControl、Write、Read

若遇到提示:“SharePoint租户管理员不允许网站集管理员更新应用程序权限。请联系您的SharePoint租户管理员”,则请参考本文末尾的附录所给出的信息。

5、在跳转界面中,为SharePoint应用授予信任

获取访问令牌

在调用REST API前需要获取访问令牌,即需要向Azure访问控制服务(ACS)的API接口发送相关信息,若通过验证,则会返回访问令牌。


API接口:

Url:https://accounts.accesscontrol.windows.net/{tenant_id}/tokens/OAuth/2                   # Global
Url:https://accounts.accesscontrol.chinacloudapi.cn/{tenant_id}/tokens/OAuth/2              # 21Vianet

Method:POST
Content-Type: application/x-www-form-urlencoded

Request Body:
client_id={client_id}@{tenant_id}
&client_secret={client_secret}
&resource=00000003-0000-0ff1-ce00-000000000000/{tenant_name}.sharepoint.com@{tenant_id}     # Global
&grant_type=client_credentials

参数说明:

参数 说明
tenant_name Azure租户的名称
tenant_id Azure租户的ID
client_id 所注册的SharePoint应用的客户端ID
client_secret 所注册的SharePoint应用的客户端密码

以上参数指的是被大括号括起来的参数,需要重点注意:Request Body里的client_id不等于SharePoint应用的客户端ID,还需要拼接@符号以及Azure租户ID。

调用REST API读取SharePoint上的文件

SharePoint的REST API有很多,这里只介绍读取文件所用到的API,若对其它API感兴趣,可以自行浏览官方文档:了解 SharePoint REST 服务

API接口:

Url:https://{site_url}/_api/web/GetFolderByServerRelativeUrl('{folder_path}')/Files('{file_name}')/$value

Method:GET
Request Headers: {Authorization:访问令牌}

参数说明:

1. site_url

SharePoint站点的根目录,可打开对应的SharePoint站点后从浏览器上方的地址栏处获得,格式为:https://TenantName.sharepoint.com/sites/SiteName

2. folder_path

需要读取的文件所位于的文件夹的路径,可使用绝对路径或相对路径。绝对路径以/SiteName为起点,注意前面的斜杠;相对路径则以Shared Documents为起点,注意前面是没有斜杠的。

为帮助理解,以下图所示的Excel文件所在位置为例,其所位于的文件夹的路径为:

绝对路径:/SharePointRESTAPIReadFile/Shared Documents/TestFolder

相对路径:Shared Documents/TestFolder

3. file_name

需要读取的文件的文件名,含格式后缀

4. Authorization

前面获取到的访问令牌,需要作为请求头参数传递


当所有参数都获取到后,就可以调用该REST API读取文件了。以Python为例,调用REST API读取文件的代码如下:

site_url = "https://TenantName.sharepoint.com/sites/SiteName"

response = requests.get(
    "{0}/_api/web/GetFolderByServerRelativeUrl('Shared Documents/FolderName1/FolderName2/../FolderName')/Files('ExcelFileName.xlsx')/$value".format(site_url),
    headers={'Authorization':token}
)

if response.status_code in [200,202]:
    with io.BytesIO(response.content) as fh:
        df = pd.read_excel(fh, sheet_name='SheetName')
        print(df)
else:
    print(response.text)

完整的实现代码

为方便使用,下面给出从获取访问令牌到调用REST API读取SharePoint文件的完整实现代码,仅支持Global环境。


PowerQuery :

let
    // Define a function to acquire access token. 
    get_access_token = (TENANT_NAME,TENANT_ID,CLIENT_ID,CLIENT_SECRET) => let
        /*
        Author: 夕枫
        Function Description: 
            Generates and returns Access token, only support global environment. 
        Returns:
            string: Access token

        TENANT_NAME :           Name of the Azure tenant
        TENANT_ID :             Id of the Azure tenant
        CLIENT_ID :             Client Id from the appregnew.aspx page. 
        CLIENT_SECRET :         Client Secret from the appregnew.aspx page
        */

        AUTHORITY_URL = Text.Format("https://accounts.accesscontrol.windows.net/#{0}/tokens/OAuth/2",{TENANT_ID}),
        RESPONSE = 
            Web.Contents(
                AUTHORITY_URL,
                [
                    Headers = [#"Content-Type"="application/x-www-form-urlencoded"],
                    Content = 
                        Text.ToBinary(
                            Text.Format(
                                "client_id=#[client_id]&client_secret=#[client_secret]&resource=#[resource]&grant_type=client_credentials",
                                [
                                    client_id = CLIENT_ID & "@" & TENANT_ID,
                                    client_secret = CLIENT_SECRET,
                                    resource = Text.Format("00000003-0000-0ff1-ce00-000000000000/#{0}.sharepoint.com@#{1}",{TENANT_NAME,TENANT_ID})
                                ]
                            )
                        )
                ]
            )
        in "Bearer " & Json.Document(RESPONSE)[access_token],

    // ------------------------------------------------config setting-----------------------------------------------------

    // PS: Only support global environment. 

    // Name of the Azure tenant
    TENANT_NAME = "Input your Tenant Name here.",

    // Id of the Azure tenant
    TENANT_ID = "Input your Tenant ID here.",

    // Client Id from the appregnew.aspx page. 
    CLIENT_ID = "Input your Client ID here.",

    // Client Secret from the appregnew.aspx page
    CLIENT_SECRET = "Input your Client Secret here.",

    // ------------------------------------------------config setting end-------------------------------------------------

    TOKEN = get_access_token(TENANT_NAME,TENANT_ID,CLIENT_ID,CLIENT_SECRET),

    // -------------------------------------------Call REST API by above TOKEN--------------------------------------------

    // CallAPI = xxxxx
    Site_Url = "https://TenantName.sharepoint.com/sites/SiteName",
    Response = 
        Web.Contents(
            Site_Url & "/_api/web/GetFolderByServerRelativeUrl('Shared Documents/FolderName1/FolderName2/../FolderName')/Files('ExcelFileName.xlsx')/$value",
            [
                Headers = [Authorization=TOKEN]
            ]
        ),
    Result = Excel.Workbook(Response,true,null)
in
    Result

Python :

import requests
import os,io
import pandas as pd

def get_access_token(TENANT_NAME,TENANT_ID,CLIENT_ID,CLIENT_SECRET):
    '''
    Author: 夕枫
    Function Description: 
        Generates and returns Access token, only support global environment. 
    Returns:
        string: Access token

    TENANT_NAME :           Name of the Azure tenant
    TENANT_ID :             Id of the Azure tenant
    CLIENT_ID :             Client Id from the appregnew.aspx page. 
    CLIENT_SECRET :         Client Secret from the appregnew.aspx page
    '''

    AUTHORITY_URL = 'https://accounts.accesscontrol.windows.net/{0}/tokens/OAuth/2'.format(TENANT_ID)
    Response = requests.post(
        AUTHORITY_URL,
        headers={'Content-Type':'application/x-www-form-urlencoded'},
        data={
            'client_id':CLIENT_ID+'@'+TENANT_ID,
            'client_secret':CLIENT_SECRET,
            'resource':'00000003-0000-0ff1-ce00-000000000000/{tenant_name}.sharepoint.com@{tenant_id}'.format(tenant_name=TENANT_NAME,tenant_id=TENANT_ID),
            'grant_type':'client_credentials'
        }
    )
    return 'Bearer ' + Response.json()['access_token']

if __name__ == '__main__':
    # ----------------------------------------------------------config setting----------------------------------------------------------------
    # PS: Only support global environment. 

    # Name of the Azure tenant
    TENANT_NAME = 'Input your Tenant Name here.'

    # Id of the Azure tenant
    TENANT_ID = 'Input your Tenant ID here.'

    # Client Id from the appregnew.aspx page. 
    CLIENT_ID = 'Input your Client ID here.'

    # Client Secret from the appregnew.aspx page
    CLIENT_SECRET = 'Input your Client Secret here.'

    # ---------------------------------------------------------config setting end---------------------------------------------------------------

    token = get_access_token(TENANT_NAME,TENANT_ID,CLIENT_ID,CLIENT_SECRET)

    # Get Excel File Content
    site_url = 'https://TenantName.sharepoint.com/sites/SiteName'

    response = requests.get(
        "{0}/_api/web/GetFolderByServerRelativeUrl('Shared Documents/FolderName1/FolderName2/../FolderName')/Files('ExcelFileName.xlsx')/$value".format(site_url),
        headers={'Authorization':token}
    )

    if response.status_code in [200,202]:
        with io.BytesIO(response.content) as fh:
            df = pd.read_excel(fh, sheet_name='SheetName')
            print(df)
    else:
        print(response.text)

    os.system('pause')

附录

2023年8月30日起,微软为增强SharePoint的管理治理安全措施,修改了通过AppRegNew.aspx页面注册的SharePoint应用程序的默认过程和通过AppInv.aspx页面进行权限更新的默认过程。经过修改后,SharePoint网站集管理员将无法通过上述页面注册应用程序或更新应用程序权限,除非SharePoint租户管理员明确授权。该调整措施只针对SharePoint应用,通过Azure门户进行应用注册和权限更新不受此更改的影响。

若无授权,当尝试在AppRegnew.aspx页面上注册应用程序时,将提示:“SharePoint租户管理员不允许网站集管理员创建Azure访问控制(ACS)主体。请联系您的SharePoint租户管理员”。同样,尝试在AppInv.aspx页面上更新应用程序权限时,也将提示:“您的 SharePoint租户管理员不允许网站集管理员更新应用程序权限。请联系您的SharePoint租户管理员”。

此外,从2018年11月7日起,Azure访问控制服务(ACS)已停用,该服务的停用不影响SharePoint应用的使用,但会默认禁用新租户使用SharePoint应用来获取访问令牌,即会让新租户的SharePoint应用获取到的访问令牌无效。但可以通过更改SharePoint租户的属性来更改该行为。

根据以上信息,若想通过注册SharePoint应用来获取访问令牌并调用REST API,需要更改两个SharePoint租户属性,其中与ACS相关的一个属性必须打开。而另一个与网站集管理员是否有权限注册SharePoint应用及更新应用权限的属性则是可选的,若你是Azure租户的全局管理员或SharePoint租户管理员,则无需打开该属性,可以使用管理员链接来注册SharePoint应用并授权,否则需要联系管理员打开该属性以获得相应权限。

设置SharePoint租户属性的方法请参考下方。


PowerShell安装SharePoint Online命令行管理程序:

想要更改SharePoint租户属性,需要先安装相应的PowerShell命令行管理程序,具体步骤如下:

1、以管理员身份打开PowerShell

2、运行以下命令查看是否已安装SharePoint Online命令行管理程序

Get-Module -Name Microsoft.Online.SharePoint.PowerShell -ListAvailable | Select Name,Version

3、运行以下命令安装SharePoint Online命令行管理程序,安装过程若遇询问,则全部同意,若已安装则可跳过该步骤

Install-Module -Name Microsoft.Online.SharePoint.PowerShell

4、若不是新安装,则最好运行以下命令更新SharePoint Online命令行管理程序

Update-Module -Name Microsoft.Online.SharePoint.PowerShell

PowerShell连接到SharePoint Online:

安装完SharePoint Online命令行管理程序后,需要先连接到SharePoint,然后才可以更改相应的属性。连接SharePoint的方式很简单,只需执行以下命令,然后输入SharePoint租户管理员的账号密码即可。

Connect-SPOService -Url https://{tenant_name}-admin.sharepoint.com

注意将命令中的tenant_name修改成真实的Azure租户名称。

允许使用 Azure 访问控制 (ACS) 仅限应用程序的访问令牌访问 SharePoint:

Set-SPOTenant -DisableCustomAppAuthentication $false

若上面的属性没有设置成false,调用REST API时则会报以下错误:

{"error":"invalid_request","error_description":"Token type is not allowed."}

允许网站集管理员创建或更新SharePoint服务主体:

Set-SPOTenant -SiteOwnerManageLegacyServicePrincipalEnabled $true 

使用管理员链接注册SharePoint应用并授权:

当具有全局管理员或SharePoint租户管理员的权限时,可以使用管理员专用链接来进行SharePoint应用的注册以及授权。

SharePoint应用注册的管理员链接:

https://{tenant_name}-admin.sharepoint.com/_layouts/15/appregnew.aspx

SharePoint应用授权的管理员链接:

https://{tenant_name}-admin.sharepoint.com/_layouts/15/appinv.aspx

除链接发生更改外,应用注册及授权的步骤与上文一致,具体步骤请参考上文。


下面是一些相关的官方文档:

[1] 通过 SharePoint 仅限应用令牌授予访问权限

[2] SharePoint 中的加载项权限

[3] SharePoint Online命令行管理程序入门

[4] 托管于德国、中国或美国政府环境中的租户的授权注意事项

[5] 了解 SharePoint REST 服务

未经允许不得转载:夕枫 » 通过REST API读取SharePoint Online上的文件 - 服务主体模式
订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论