最近写的后端项目需要云盘,由于各种原因,最终选择了 Onedrive。Onedrive 的一个优点是文档是中文的,但缺点是中文也看不懂。。。。
于是借助文档、博客、示例代码等等,慢慢摸索过来,并把摸索的这个过程形成一篇博文。
注册应用、用户登录授权
参考博客:zhangguanzhang’s Blog
参考文档:Microsoft Graph 中的 OneDrive 授权和登录
Onedrive 的认证(以及 MS 家的其他功能)都统一使用 Microsoft Graph 进行认证,而 Microsoft Graph 似乎只支持 OAuth2(必须让用户在浏览器完成登录,不能拿到密码然后在后台登录),不支持 OAuth1(可以在后台利用用户密码发起请求完成登录),所以会麻烦一些。
在 Azure 创建应用
第一件事,是按照上面的博客所说,注册应用。这个应用其实就是一个 API,以下引用并修改了 zhangguanzhang’s Blog:
我们首先要创建一个应用程序。
https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
到上面链接里去注册一个应用程序,属性为:
- 受支持的帐户类型记得选
任何组织目录(任何 Azure AD 目录 - 多租户)中的帐户和个人 Microsoft 帐户(例如,Skype、Xbox)
- 重定向 URI (可选) 我用的是
http://localhost:8000/getAToken
,在摸索的时候这个随便写就行。另外,这个值要存在代码里,命名为redirect_uri
。
然后设置权限。点击左边侧边栏的 “API 权限”,点击中间的“添加权限”,然后在右边点“Microsoft Graph” - “委托的权限”,搜索并找到以下三个权限,勾选上。
1 | Files.ReadWrite |
然后在侧边栏的“概述”里面,把应用程序(客户端) ID 复制下来(在代码里存为 client_id
)。
然后在侧边栏“证书和密码”里面,点击“新客户端密码”,生成一个 ID 和值,把值存到代码里,命名为 client_secret
。
用户在浏览器登录,获取 auth_token
在 Azure 创建应用以后,就是授权直至拿到 refresh_token
的过程。这个过程就是参考 Microsoft Graph 中的 OneDrive 授权和登录 了,这里我用 Python requests 实现。
现在文档迁移到了 Microsoft Graph 身份验证概述而且更加详细了。而上述链接不再提供中文文档。
首先设置上述变量:
1 | import requests |
然后是生成登录链接。这个链接需要在浏览器里访问然后输入账号密码,于是就不用 requests.get
而是把链接 print 出来,我们复制到浏览器里面。
1 | login_link = f"https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={client_id}&scope={scope}&response_type=code&redirect_uri={redirect_uri}" |
打开 print 的链接,成功登录并授权后,跳转到 localhost
,其中的参数的 code
字段就是我们需要的 auth_token
,729个字符,有点长。
1 | http://localhost:8000/getAToken?code=0.AAAApOutbSzcpEiz8KuV1mGJ2ip56jr19aZJtk3hpF03UyM-AI4.AQABAAIAAAD--DLA3VO7QrddgJg7Wevry9C8xxU0YmDl1t3kRL1Rt8c4ZYINbxBW_X7KGMwL80bFg2I99rHKlQuC_bwGWIMjn1C4GjZg8fR2v2r-9J6fffSYUVMmrA5tGJ-7AUyulg076ViCOLWvtDzUh1T09f3Tt2Q8TpgCgO8P-0PqGMPqNOivzAxQz8WH5ZKSTMaI7WeOSBGe9yrpdjskO4pJrZv62E-jl2udaTBSXJAG4hKc18feCvuhJk27gT4H1W7ZiONqwMMkpzK6nlMhBgRP7-hTBNIU82Y0ASNOsOu2aAzrCQJmmbDdPHvsEYIq5jDnlOqeoNNFh-0v_AEbf-YfUfvIxN79eGpgrXvH19sLstDqFagJaOayNm6sf4HHuo2ikAot5kLZoBwYus57BaWeLI2IA_jDKd9T899Pv_Gfc_fwkgI9PynaHfoDRHb9A-6fXaJYPE5IYkTEnunNDaBf6jKtoTPub5LFIZv3OP38c3zJrTBZL5Wr5dpo3-pa0FFkbYPgHGC5APlWFNiBx-OECd4OJnbdzM7hrA2YzCLa6Bwl7SG4KTVXv2fwW1gFLgCZdI_xYBEDHfYuKUnlC5eqcebWwtkfJ8roFj9p3hzJ_GQSgEKjTgrdSUMaxrYwhSnAzS06H4BqnLKL-FrKsDBWJXuAIAA&session_state=697ec6eb-a4b9-4567-9873-6065f156164b# |
在之后开发 WebApp 时,我们需要把 redirect_url
设置为我们能路由到的链接,在路由到的 View 中获取 uri 参数,就可以获得 auth_token
了。不过现在还是手动赋值吧。
1 | # 赋值 |
用 auth_token 换 access_token 和 refresh_token
赋值以后,就可以请求拿 access_token
和 refresh_token
了。
1 | headers={'Content-Type':'application/x-www-form-urlencoded'} |
好耶!
MS 还提供了网址对 access_token 进行解密:https://jwt.ms/
。
用 refresh_token 换新的 access_token 和新的 refresh_token
然后,我们把上面 response 里的 refresh_token
取出来,然后请求刷新 access_token
和 refresh_token
:
1 | refresh_token = eval(response.content)['refresh_token'] |
至此,关于 token 的申请方法就讲完了。
Django 实现
我把上面的过程改成了一个 OnedriveAuthentication
类(代码见 GitHub,views 部分在 views.py 中)。
另外,MS 也给了一个利用 requests_oauthlib.OAuth2Session
封装的示例,也可以参考。
另外需要注意的是,如果在 Django 后端中使用 requests
请求 Onedrive,会使得线程等待,在 Onedrive 相关请求并发量高的时候很容易造成数秒甚至数十秒的延迟。
可以在部署的时候使用更多线程缓解这个症状。想要根本改变这个问题,就不得不提到异步了。
异步请求可以使用 aiohttp
库。Django 3.1 也提供了异步视图的支持(但没有提供异步数据库访问的支持,不过也提供了临时解决办法)。但是,Django REST Framework 3.2.3 还不支持异步视图,想要使用异步视图,还得使用纯 Django 视图。
所以,目前我的解决办法,是把并发最高的一个 API 改写为纯 Django 视图然后异步化,并对该视图用到的 requests
请求改为 aiohttp
请求。
在前后端分离的情况下使用 Onedrive API
完成登录授权,拿到 Access Token 以后,就可以用 MS Graph 的 API 进行操作了。可以在 Graph Explorer 进行测试。
而具体使用方法,就直接看文档啦。这文档蛮详细的,边写边看就行。
由于 REST API 的寻址部分比较麻烦,我使用 Python 对 API 进行了简单封装,代码在 GitHub。
这里只写了自己在开发过程中遇到的一些场景,以及解决方案。
项目的场景是使用一个账户的云盘作为整个项目的共有云盘,文件上传、删除等操作全部交给后端完成。后端判断前端登录的用户权限后,使用该账户的 access_token 执行操作。
上传
为了省去文件经过后端服务器的流量,后端可以使用 Onedrive 的通过上传会话上传大文件 方案上传文件。前后端和 Onedrive 的交互过程如下:
- 在登陆状态下,前端向后端
POST /api/cloud/file
发起请求,后端请求 Onedrive 生成一个临时上传对话,并将 Onedrive 的应答(格式见上面的链接)转发给前端; - 前端按照上面链接所述方法,直接向 Onedrive 上传文件。上传完成后,Onedrive 返回文件的 id 等信息,文件将位于
/(应用文件夹)/temp/{userid}/
文件夹; - 前端根据需求(如上传沙龙相关文件、沙龙照片)向对应接口发起请求(请求需包含文件 id),后端将文件移动至每个功能对应的文件夹,并完成后续操作(录入数据库等)。
前端交互
前端交互原理很简单好像也不简单,对于错误处理就更麻烦了,虽然 MS Graph 最后给了对各种错误处理的详细策略,但是没有示例代码还是很麻烦。
因此,这里放一个我的 JavaScript 实现,其中 createUploadSession
是创建上传会话的接口。
1 | // 调用后端接口,将文件上传至 onedrive。 |
这段代码还没有加上传进度等功能,不过上传的大体思路就是这样的。
下载
为了省去文件经过后端服务器的流量,本后端只提供下载链接。由于 Onedrive API 限制,提供两种下载链接:
- 永久链接,但需在浏览器中访问
- 临时链接(Onedrive 链接有效期 15min,但本后端提供即时获取链接并重定向的 REST API)
永久链接
有网友发现,手动或利用 Onedrive API 生成分享链接后,在分享链接后追加 ?download=1
参数,在浏览器访问该链接即可自动下载文件。
但该链接对应的 html 需要运行 JavaScript,因此不能通过 Python requests
或 JavaScript XMLHTTPRequest
直接下载。推荐使用后面的 API,或者在 JavaScript 使用 window.open()
在新窗口打开这个链接。
临时链接
利用 Onedrive API 下载文件 时,响应报文为 302 Found
,Location
为一个下载 URL。该 URL 仅在较短的一段时间 (几分钟后)内有效,不需要认证即可下载。
本后端提供 /cloud/file/{id}/download/
API,该 API 调用上述 API 后将报文返回给前端。