最近写的后端项目需要云盘,由于各种原因,最终选择了 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
2
3
Files.ReadWrite
Files.ReadWrite.All
offline_access
勾选权限
勾选权限

然后在侧边栏的“概述”里面,把应用程序(客户端) ID 复制下来(在代码里存为 client_id)。

然后在侧边栏“证书和密码”里面,点击“新客户端密码”,生成一个 ID 和值,把值存到代码里,命名为 client_secret

用户在浏览器登录,获取 auth_token

在 Azure 创建应用以后,就是授权直至拿到 refresh_token 的过程。这个过程就是参考 Microsoft Graph 中的 OneDrive 授权和登录 了,这里我用 Python requests 实现。

首先设置上述变量:

1
2
3
4
5
6
7
import requests
headers = {'Content-Type':'application/x-www-form-urlencoded'}
scope = "Files.ReadWrite offline_access"

client_id = "3aea792a-f5f5-49a6-b64d-e1a45d375323"
redirect_uri = "http://localhost:8000/getAToken"
client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

然后是生成登录链接。这个链接需要在浏览器里访问然后输入账号密码,于是就不用 requests.get 而是把链接 print 出来,我们复制到浏览器里面。

1
2
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(login_link)

打开 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
2
# 赋值
auth_token = "0.AAAApOutbSzcpEiz8KuV1mGJ2ip56jr19aZJtk3hpF03UyM-AI4.AQABAAIAAAD--DLA3VO7QrddgJg7WevrnDiEeOedZvv94_iCOZdHtunnh-HD4-yA7CnKCnlskkTcXuD_nVujxrzDErLOPO5Kdba-gLFaUgOCxScnphpTmaR-bJbb6KCOJ6WEsecTZhdoBVvhgG8SzPAmolHemGaOOymSx1L3opaGXSo6YolsgwSCYcokocf9jl9Jq8pOAfg4tuZTYh1uX6f-IvQgLoIhUhE8i7GoaLIFhel9ICjiWgO9imtgNiLIjMX-MMRxqEKbcvbjVi9vnat1LE29v7uX_0Ogs6feGFzKEZOvT8pNlbm1t0frSwSIktQeIELEU3kzbDX7TEp1b_Cguj2u7wqF-kLv3P9w2_Gzhi-lzdRph1h9SW-RFYyWtdoCfhQgFf5m8K7aUGUEKgDK0RomoPvuZ1jfoNxe5JWQdcHqNo-LcHblYGRI1azy-p1fp28aBB6qKZ_0pcRTb4BrU5qLi9ze4RJ5kPajodTSSloUSVkf64B9OEdPeBvT8XU8_to0aVeHXOUTlsgVSlliatLPx9pFhsOZ4ec2-7fQMr8_CBhPlY4rjMfl8plefsfwcIsDBc0PRPITcsxA7OYSxZXvSchHA6XmCSzdl1413mTh7d0cvLYnBmzJm1tc6z6PEW270mtL_enHuzbtZpK9D1Epu0HIIAA"

用 auth_token 换 access_token 和 refresh_token

赋值以后,就可以请求拿 access_tokenrefresh_token 了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
headers={'Content-Type':'application/x-www-form-urlencoded'}
response = requests.post(
'https://login.microsoftonline.com/common/oauth2/v2.0/token',
data={
'client_id': client_id,
'redirect_uri': redirect_uri,
'client_secret': client_secret,
'code': auth_token,
'grant_type': 'authorization_code'
},
headers=headers)

response # <Response [200]>
eval(response.content)
# {'token_type': 'Bearer',
# 'scope': 'Files.ReadWrite profile openid email',
# 'expires_in': 3599,
# 'ext_expires_in': 3599,
# 'access_token': 'eyJ0eXAiOiJKV1QiLCJub25jZSI6ImZCREdZc3JfSzVYLXBkU2o5ZWotUUJMc2JGRGFDVGFBQ0lvSk90T3I2UlEiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC82ZGFkZWJhNC1kYzJjLTQ4YTQtYjNmMC1hYjk1ZDY2MTg5ZGEvIiwiaWF0IjoxNjE0MjYyMDgzLCJuYmYiOjE2MTQyNjIwODMsImV4cCI6MTYxNDI2NTk4MywiYWNjdCI6MCwiYWNyIjoiMSIsImFjcnMiOlsidXJuOnVzZXI6cmVnaXN0ZXJzZWN1cml0eWluZm8iLCJ1cm46bWljcm9zb2Z0OnJlcTEiLCJ1cm46bWljcm9zb2Z0OnJlcTIiLCJ1cm46bWljcm9zb2Z0OnJlcTMiLCJjMSIsImMyIiwiYzMiLCJjNCIsImM1IiwiYzYiLCJjNyIsImM4IiwiYzkiLCJjMTAiLCJjMTEiLCJjMTIiLCJjMTMiLCJjMTQiLCJjMTUiLCJjMTYiLCJjMTciLCJjMTgiLCJjMTkiLCJjMjAiLCJjMjEiLCJjMjIiLCJjMjMiLCJjMjQiLCJjMjUiXSwiYWlvIjoiRTJaZ1lORGY2OWJFVUxnd3BkR0FMM2J5aHZRZCsxNkg2THppdUwvNGxPejYrR25mbE1VQSIsImFtciI6WyJwd2QiXSwiYXBwX2Rpc3BsYXluYW1lIjoi6Ziu6JaH6JaH54K55ZCN5ZWmIiwiYXBwaWQiOiIzYWVhNzkyYS1mNWY1LTQ5YTYtYjY0ZC1lMWE0NWQzNzUzMjMiLCJhcHBpZGFjciI6IjEiLCJpZHR5cCI6InVzZXIiLCJpcGFkZHIiOiIxNTQuMTcuNy44NCIsIm5hbWUiOiLnlLXlrZDnp5HmioDlpKflraZNU0MiLCJvaWQiOiIyZDU1MTQyNS01Mzk2LTQxNTktOTQ1MC0wNzU2MzdiMDE2N2IiLCJwbGF0ZiI6IjMiLCJwdWlkIjoiMTAwMzIwMDA1N0RCODYzRSIsInJoIjoiMC5BQUFBcE91dGJTemNwRWl6OEt1VjFtR0oyaXA1NmpyMTlhWkp0azNocEYwM1V5TS1BSTQuIiwic2NwIjoiRmlsZXMuUmVhZFdyaXRlIHByb2ZpbGUgb3BlbmlkIGVtYWlsIiwic3ViIjoid2R3RDhmLXM0ajJDWHlmQ2phOWd1LXpVdk91V0ZUWFg2bXk1d0ZGODROcyIsInRlbmFudF9yZWdpb25fc2NvcGUiOiJBUyIsInRpZCI6IjZkYWRlYmE0LWRjMmMtNDhhNC1iM2YwLWFiOTVkNjYxODlkYSIsInVuaXF1ZV9uYW1lIjoidWVzdGNtc2NAZGVtbzRjLm9ubWljcm9zb2Z0LmNvbSIsInVwbiI6InVlc3RjbXNjQGRlbW80Yy5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJFQ0Vaei1LeEprZVhOMDRTYlVNa0FBIiwidmVyIjoiMS4wIiwid2lkcyI6WyJiNzlmYmY0ZC0zZWY5LTQ2ODktODE0My03NmIxOTRlODU1MDkiXSwieG1zX3N0Ijp7InN1YiI6Inl0WGtlTWM0dFVnV3dpMmJLbS1xc3JMazJuY3Zjc3lrYVFYcm1Zdm1lRE0ifSwieG1zX3RjZHQiOjE0OTcyNDg0OTN9.EjpbLnqwdSsjeQwy5oVWjqHfSjFyHwQHcwNNvVhq8w9J96PQEjx6xpyIc-VgrQc0h1DdfikzyOVkMEHSVifM_KZoaaUIcW3T4xgXjgZVeEtznAKEqSDB4PN5qR45hsZbJLoVFBxaRZsQrEiPK7m2F4-4_VCK-I6Dfhvhm2_XTiBXnbqSsQ87VhF01BqSVXBIlJwSS1mQKEYHJGCjKnTB5yToDzJvoh4p7GGY3SCmfWY-pDMsAvMlJbErfhXh9Hxc41eBESSEmoajZAnxFOJU5LRtVHqPQW6PwlSTHL2nlGfdXFpspTc6hy4pSyVDNq6I3HJRFXl1cW0L6DjD2FoWMw',
# 'refresh_token': '0.AAAApOutbSzcpEiz8KuV1mGJ2ip56jr19aZJtk3hpF03UyM-AI4.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P_bTRjPRZzMorqH901Y19Ppp0sTRQdBBKFPHBjjxCNhD6fQlBevSxmidflskapxgyxbPlJysKnmnXMvEw6oy9tTRGn5QAB6kuYsP9BVFNiEpqeyqyyABuuiZZXHfMVSiqcIuUYdC5Cbt-meckpgVSC9Dfhvr50ilZ99m5iUHDkFVJLdPQLlrgdHqV8Mhe1V6Eh7F5Mfqrg1xb-K-QLZ3EGrcBUsDarXTkKPWP_9rKgukN_PTFerEBaM90u--8iytv0RJAui31HTa4yIInU0g7vZ16fhaEvJSXQjMSc5TYeepCHkK2fi3UpJe8TOoQ-qpORU_CJl83YMF59_enWxi0f_5ouV_9CPpeZ0xReU1Nb2y4g7uBYVPPNyroVlSmU0zV9c_rgs-dkSXK3b6r-AN-c5msS_ZoGvjiL3qBevNKo3Ws4w4IBWO-wQV8arNtNSuCAv5u9z41gITSDDg1Z8EYizTjQHjVaP095n5CWcBghqboVL4H2AAzW1SIgDJIs7ybwhg0oVsjUW4s9FQguPe9lj3HrAHzk5_NMRJsuGIZpDJ3ENz2pldXaRBqk42X07_y549Aw3qmgd8UY2KqsbA0EMNharkf-3q0_4AyeyxtqSqJETF5F3wy0bQ0RViS5W5mVobaRU90ia-zHzOjvXtxvenaY-r2ANjh_yg3qiV2D8Z7ODW50YzLL9SgiFqMm7zg7RYd4jjpdTYOik2yCX7cn_sSaA29KAqkbWKZNqX2Ds_ZRyby8RYeHBOC_I0XbrngLHnTszik-eKI3rHBv7UdTXCM9PHJwl2X4pocIREzfFPOTnLauiyfAS_zpb2Lsw8exVEqIpesVZt1S8uMvaEPctk7P22myx-vdxrk937c9z368vGpN6dBS--LPq5ZaxX9TuxCRbfCesT6KpkgLMq6Ywho59Tc6cAeE18sw'}

好耶!

MS 还提供了网址对 access_token 进行解密:https://jwt.ms/

用 refresh_token 换新的 access_token 和新的 refresh_token

然后,我们把上面 response 里的 refresh_token 取出来,然后请求刷新 access_tokenrefresh_token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
refresh_token = eval(response.content)['refresh_token']

response_refresh = requests.post(
'https://login.microsoftonline.com/common/oauth2/v2.0/token',
data = {
'client_id': client_id,
'redirect_uri': redirect_uri,
'client_secret': client_secret,
'refresh_token': refresh_token,
'grant_type': 'refresh_token'
},
headers = headers)

response # <Response [200]>
eval(response.content)
# {'token_type': 'Bearer',
# 'scope': 'Files.ReadWrite profile openid email',
# 'expires_in': 3599,
# 'ext_expires_in': 3599,
# 'access_token': 'eyJ0eXAiOiJKV1QiLCJub25jZSI6ImhFSy1UT0xyZC1WZVNQOGVCU3plTURzeW1EODk5RWdlMEJzVDM5ak5uZEkiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC82ZGFkZWJhNC1kYzJjLTQ4YTQtYjNmMC1hYjk1ZDY2MTg5ZGEvIiwiaWF0IjoxNjE0MjYyNjg4LCJuYmYiOjE2MTQyNjI2ODgsImV4cCI6MTYxNDI2NjU4OCwiYWNjdCI6MCwiYWNyIjoiMSIsImFjcnMiOlsidXJuOnVzZXI6cmVnaXN0ZXJzZWN1cml0eWluZm8iLCJ1cm46bWljcm9zb2Z0OnJlcTEiLCJ1cm46bWljcm9zb2Z0OnJlcTIiLCJ1cm46bWljcm9zb2Z0OnJlcTMiLCJjMSIsImMyIiwiYzMiLCJjNCIsImM1IiwiYzYiLCJjNyIsImM4IiwiYzkiLCJjMTAiLCJjMTEiLCJjMTIiLCJjMTMiLCJjMTQiLCJjMTUiLCJjMTYiLCJjMTciLCJjMTgiLCJjMTkiLCJjMjAiLCJjMjEiLCJjMjIiLCJjMjMiLCJjMjQiLCJjMjUiXSwiYWlvIjoiQVNRQTIvOFRBQUFBcFB5WUtCamlUK2kxWnVaY3BLNitudHh2ektkMGovNGtvSVhGVnBwOE5vQT0iLCJhbXIiOlsicHdkIl0sImFwcF9kaXNwbGF5bmFtZSI6IumYruiWh-iWh-eCueWQjeWVpiIsImFwcGlkIjoiM2FlYTc5MmEtZjVmNS00OWE2LWI2NGQtZTFhNDVkMzc1MzIzIiwiYXBwaWRhY3IiOiIxIiwiaWR0eXAiOiJ1c2VyIiwiaXBhZGRyIjoiMTU0LjE3LjcuODQiLCJuYW1lIjoi55S15a2Q56eR5oqA5aSn5a2mTVNDIiwib2lkIjoiMmQ1NTE0MjUtNTM5Ni00MTU5LTk0NTAtMDc1NjM3YjAxNjdiIiwicGxhdGYiOiIzIiwicHVpZCI6IjEwMDMyMDAwNTdEQjg2M0UiLCJyaCI6IjAuQUFBQXBPdXRiU3pjcEVpejhLdVYxbUdKMmlwNTZqcjE5YVpKdGszaHBGMDNVeU0tQUk0LiIsInNjcCI6IkZpbGVzLlJlYWRXcml0ZSBwcm9maWxlIG9wZW5pZCBlbWFpbCIsInN1YiI6Indkd0Q4Zi1zNGoyQ1h5ZkNqYTlndS16VXZPdVdGVFhYNm15NXdGRjg0TnMiLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiQVMiLCJ0aWQiOiI2ZGFkZWJhNC1kYzJjLTQ4YTQtYjNmMC1hYjk1ZDY2MTg5ZGEiLCJ1bmlxdWVfbmFtZSI6InVlc3RjbXNjQGRlbW80Yy5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJ1ZXN0Y21zY0BkZW1vNGMub25taWNyb3NvZnQuY29tIiwidXRpIjoiUjZERXd0TWlBazJsaTVXaWRIOGpBQSIsInZlciI6IjEuMCIsIndpZHMiOlsiYjc5ZmJmNGQtM2VmOS00Njg5LTgxNDMtNzZiMTk0ZTg1NTA5Il0sInhtc19zdCI6eyJzdWIiOiJ5dFhrZU1jNHRVZ1d3aTJiS20tcXNyTGsybmN2Y3N5a2FRWHJtWXZtZURNIn0sInhtc190Y2R0IjoxNDk3MjQ4NDkzfQ.GdgiFzmdXNFuBqaDSsLzalQU0QULm1fAYZw-mKw5D1KdR4j9WSl6sIa9vPcuogg1x7oLMujcgfdyY0Cf4KNLk3fQ3vzLo395R5GDbg-djBREWCBBgFvcgbu8AyH7yj6MxdXsY59U9nFqVdeIfqEZxxxFCT2F2Nc-CfrDsI8PZQL3kn0VFcvSiUwuOMlH06ydKwyyBNhCRh5yc9x32XWwlRde-GPZCd1SdyvcPvo_mUcEJxh97tRfehHNTDltXGFsAZqcYaz18iJk9MqZ4tlyOIIWPX_lXGRtNe2dUDQE6g6znWY0ClGvU3X9mdScUUtfblmnfmzMj-ECBkAjmjKBnA',
# 'refresh_token': '0.AAAApOutbSzcpEiz8KuV1mGJ2ip56jr19aZJtk3hpF03UyM-AI4.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P89XPzxfnsn8nWMuIFittfpaEqpEzvy5sSPvkBHSIgu_y7Ppazm1F3l2nJ6hEWraRomalXRdwDi1Y78yIC_yA1xAspMEEuoxejDjoBhcWqOWLUTfXkfKk7i6pugNA-a_GOH9-EvKc_g9NvuCK4QgK6FQadMUf62wpoCkB53kgnH8GtOYKW34AutKaFi-N3v8RstrJRtsT3ZXanWmlA_q2WbDSou3-L_H6MS62u3Fr_mxnaKx4lKyy4cXaWbb_29dbx_yVO4zd_x5WHbcnrkfk3qoxD_b5kxHOMlKpVHtX_NthQvg8ZekDZ0pLawRsCDhJ8NFGwJvIXKxaMAyCbhHsyklnw97sU3M9jg08oAbaoQ0JA7eDZ4gd4juSzvkdGVK77unbROzOC1GjR12MWd9n_lYz4KMv_ErKx8wN2MHMAVK-QBBkJUsdB4JbBzSVgneD3jaDvWQRyz4BJMMx5e8qjR-bRwCBjm4Y1aK7vYNBGl2DVR1sVbf8eRZje6AxRQYy3rjdDod_30IqjNhDzX-eILlXR570RmoOGZgaWNPf-3-_3AaDGtsBE8tlCGux8lIR7jIxIKeBjfp1S5ymiofVirYYJiAg17KHy_W9FHUHS9CIs6CrKEvPesjDuFmerE14hzmJ4LCJarZSy2AsV6XiTMmamzaKX-WwFSzGXpipOwPbcO4tjcq5tFWz7-1TzDKsaAyvvh-K3fQiD2GUAv4v9zgRVczUQXN0Z37RULpnSGhj2eXaOenNXl9EoIgBLKXpj-lOshEyRbL2P3WzgvS1MUaXH31IHt-NiQuMpmeBfGg-gxrc9inQ0n7ATfD0T5vZ8YodjccP_rJvzI1Ntfl1yQs0g2h2JyZp694MOqc7Clv8W0P8F-uJIVWK2yv3iPRz-_nBM3im0A9OrCaugArHGPJ9gRt03nHzUHeJIAMc2Xqg'}

至此,关于 token 的申请方法就讲完了。

Django 实现

我把上面的过程改成了一个 OnedriveAuthentication 类(代码见 GitHub,views 部分在 views.py 中)。

另外,MS 也给了一个利用 requests_oauthlib.OAuth2Session 封装的示例,也可以参考。

Onedrive API 使用

完成登录授权,拿到 Access Token 以后,就可以用 MS Graph 的 API 进行操作了。可以在 Graph Explorer 进行测试。

而具体使用方法,就直接看文档啦。这文档蛮详细的,边写边看就行。