前言
这几天打算把文件备份下,最后看到有网友说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
上面链接里去注册一个应用程序,属性为:
- 受支持的帐户类型记得选 任何组织目录(任何 Azure AD 目录 - 多租户)中的帐户和个人 Microsoft 帐户(例如,Skype、Xbox)
- 重定向 URI (可选) 目前我是写的自己的二级域名,开发阶段一般是个人的pc,所以建议写 http://localhost/auth,后期开发完了只留一个uri就行,也就是到时候自己访问的域名
添加完后进应用程序的证书和密码
,这里新建一个密码,记得复制保存下来
,过一段时间后密码会打码看不到,等同于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 | # curl -sk https://api.office.com/discovery/v2.0/me/ -H "Authorization: Bearer ${access_token}" | jq . |
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 | POST https://login.microsoftonline.com/common/oauth2/v2.0/token |
后面要用refresh的token去刷新token,上面这样按照官方文档请求的话是没有refresh_token
的
1 | { |
所以这里的官方文档写得有误,我自己摸索出来应该要带上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 | POST https://login.microsoftonline.com/common/oauth2/v2.0/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 | listener, err := net.Listen("tcp", bindAddress) |
另外我看rclone里的authurl多了个v2.0,之前我的是
1 | https://login.microsoftonline.com/common/oauth2/authorize |
我看oneindex和rclone的url一样,上面这个也可以,但是如果要和rclone的url一样的话必须带上scope值
1 | 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_ |