zhangguanzhang's Blog

使用microsoft-graph的api对接onedrive

字数统计: 2.2k阅读时长: 9 min
2020/02/27

前言

这几天打算把文件备份下,最后看到有网友说onedrive不错,就是容量小了点只有5g,然后酷安去安装安卓客户端,下面看到一堆人送office e5子账户的。看了下大概是微软为了推广自己产品面向开发者送的优惠,账户有免费的5t存储,而且安装office啥的免费。试用30天,30天内调用api来保持活跃度的话就会延期。
评论里一大堆都是搭建oneindex或者onelist的,主要作用就是把onedrive给抽象成一个公共的网盘使用,oneindex是php写的,我看issue居然385个,作者的github也在更新,而且作者学了golang,居然也不打算重构下,估计也是烂尾了。onelist的作者之前是pyton写的,后面用golang重构了个,但是这个比不开源,issue看样子也爱理不理的。昨晚研究了下发现网上的也挺多坑的。这里先写下个人理解,后续继续更新。
首先去弄一个e5子账户来,整个目的就是,搭建一个后端,后端拿着认证信息访问微软的api(这里主要是onedrive,看api还支持office的其他产品),例如包含了web上展示pdf,jpg,视频啥的,还可以获取文件下载直链,例如用idm多线程下载速度飞起。在用户看来他是访问一个网页,网页提供了一个不像度盘那样的流氓网盘功能。

api前期工作

图的话感觉图床都不稳定,所以博客不会放图片的

应用程序

全程我是自备了梯云纵的,首先登陆自己e5账号。微软为了限流api和权限分离整了一个机制,提供了一个虚拟的概念叫应用程序,包含一系列id和secret,拿着这些信息去用oauth2去授权调用api,还可以给这个应用程序设置api的权限,我们首先要创建一个应用程序。
https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
上面链接里去注册一个应用程序,属性为:

添加完后进应用程序的证书和密码,这里新建一个密码,记得复制保存下来,过一段时间后密码会打码看不到,等同于appSecret之类的。在API权限那点击蓝色的Microsoft Graph后右侧弹出权限,找到Files勾上下面两个权限后点击更新权限:

  • Files.ReadWrite
  • Files.ReadWrite.All

概述那把应用程序(客户端) ID的那串复制了

实际上后面我看oneindex的流程和api路径才发现它才是正确姿势,onelist都是使用那个作者的客户端id啥的我想不通为啥也可以

oauth2部分

我看了下onelist https://github.com/MoeClub/OneList/blob/master/OneList.py 是调用的office 365啥的api,结果报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# curl -sk https://api.office.com/discovery/v2.0/me/ -H "Authorization: Bearer ${access_token}" | jq .
{
"@odata.context": "https://api.office.com/discovery/v2.0/me/$metadata",
"value": [
{
"name": "allServices",
"kind": "EntitySet",
"url": "allServices"
},
{
"name": "services",
"kind": "EntitySet",
"url": "services"
}
]
}
[root@k8s-m1 ~]# curl -sk https://api.office.com/discovery/v2.0/me/services/ -H "Authorization: Bearer ${access_token}" | jq .
{
"error": {
"code": "101, Microsoft.Online.Services.O365Discovery.O365DiscoveryException",
"message": "The app ID is blocked for access of the O365 Discovery Service."
}
}

web路由allServices访问了下发现没有啥有用的信息
最后我搜索了这个message字段才发现微软是停用了api的,文章 https://developer.microsoft.com/en-us/office/blogs/outlook-rest-api-v1-0-office-365-discovery-and-live-connect-api-deprecation/ 里说的很清楚,意思就是说新注册的应用程序无法访问office365的api。
而是应该去使用Microsoft Graph的api,网址 https://developer.microsoft.com/en-us/office/blogs/migrating-from-the-office-365-discovery-service-to-microsoft-graph-to-discover-a-sharepoint-root-site/ 里给了一个java的例子,看了下整个流程我下面用shell的curl写下,https://docs.microsoft.com/zh-cn/onedrive/developer/rest-api/getting-started/graph-oauth?view=odsp-graph-online 我们使用代码流的流程访问

获取授权代码

发起get请求访问

1
https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=${client_id}&redirect_uri=${redirect_uri}

重定向的url可以设置成localhost或者自己的,客户端id写进去,这步最好浏览器访问,url会跳转成类似https://myapp.com/auth-redirect?code=df6aa589-1080-b241-b410-c4dff65dbf7c...
拿到code=后面的值,注意值里有个&,encode的时候注意下

兑换代码以获取访问令牌

1
2
3
4
5
POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
&code={code}&grant_type=authorization_code

后面要用refresh的token去刷新token,上面这样按照官方文档请求的话是没有refresh_token

1
2
3
4
5
6
7
8
{
"token_type": "Bearer",
"scope": "profile openid email 00000003-0000-0000-c000-000000000000/Files.ReadWrite.All 00000003-0000-0000-c000-000000000000/User.Read",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": "eyJ0eXAiOiJKV1QiLCJub2......."
}

所以这里的官方文档写得有误,我自己摸索出来应该要带上resource或者scope,第二步应该是

  • 带上参数resource=https://graph.microsoft.com/
  • 或者scope=offline_access files.readwrite.all 记得urlencode

用access_token去访问onedrive下的文件

该api来源于graph-explorer https://developer.microsoft.com/en-us/graph/graph-explorer/preview

1
curl -sk https://graph.microsoft.com/v1.0/me/drive/root/children -H "Authorization: Bearer ${access_token}" | jq .

返回的json里有文件的下载直链,和web上展示的链接,但是access_token是一个小时的时效的,我们得刷新token

刷新token

1
2
3
4
5
POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
&refresh_token={refresh_token}&grant_type=refresh_token

后面搜索了下发现相关的api在onelist里也有 https://github.com/donwa/oneindex/blob/master/lib/onedrive.php

后面慢慢更新

应用授权部分

理论上标准的oauth2是自己应用启动,然后打开一个链接,该链接包含了应用的id也就上上面的获取授权代码部分,浏览器打开该链接会弹出微软的授权登陆界面,就像你登陆论坛时候选择使用其他社交账号一样,授权后会回到web。这部分思路昨晚思考了许久不知道如何写得优雅些。但是肯定是会用oauth2的,酷安的onedrive下面看到有人说可以用rclone下载和上传onedrive的文件,rclone我知道是用golang写的,便上去看看它对接onedrive这部分代码是咋写的,结果还真找到了启发思路
代码里的oauth2是用的谷歌的包 https://github.com/rclone/rclone/blob/master/backend/onedrive/onedrive.go#L58-L67
获取code是先启动一个web server,利用channel阻塞住,打印那个叫你打开浏览器授权的链接后,微软会把重定向到 http://localhost/auth?code=……, 而我们的web server是handle了/auth这个路由,查询url的param的code,送到channel里,channel这个就不会阻塞住往下面流程执行,然后会关闭这个临时的web server,这部分逻辑挺机智的,位置为 https://github.com/rclone/rclone/blob/master/lib/oauthutil/oauthutil.go#L464-L520
我的代码这部分大概为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
listener, err := net.Listen("tcp", bindAddress)
if err != nil {
return nil, err
}
go func(){
_ = httpServer.Serve(listener)
}()

fmt.Printf("Please go to the following link: %s?response_type=code&client_id=%s&scope=%s&redirect_uri=%s\n",
AuthURL, applicationID, url.PathEscape(strings.Join(g.tokenConfig.Scopes, " ")), RedirectURL)
fmt.Printf("Log in and authorize goneindex for access\n")
fmt.Printf("Waiting for code...\n")
authCode = <- codeCh //获取code,用户没有打开链接授权则会阻塞在此
fmt.Printf("Got code\n")

fmt.Printf("Closing auth server\n")
close(codeCh)
_ = listener.Close()
// close the server
_ = httpServer.Close()

另外我看rclone里的authurl多了个v2.0,之前我的是

1
https://login.microsoftonline.com/common/oauth2/authorize

我看oneindex和rclone的url一样,上面这个也可以,但是如果要和rclone的url一样的话必须带上scope值

1
2
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code&client_id=${client_id}&scope=Files.ReadWrite%20Files.ReadWrite.All%20offline_access&redirect_
uri=http://localhost:8080/auth

一些其他文章链接

CATALOG
  1. 1. 前言
  2. 2. api前期工作
    1. 2.1. 应用程序
    2. 2.2. oauth2部分
      1. 2.2.1. 获取授权代码
      2. 2.2.2. 兑换代码以获取访问令牌
      3. 2.2.3. 用access_token去访问onedrive下的文件
      4. 2.2.4. 刷新token
    3. 2.3. 应用授权部分
  3. 3. 一些其他文章链接