异想之旅:本人原创博客完全手敲,绝对非搬运,全网不可能有重复;本人无团队,仅为技术爱好者进行分享,所有内容不牵扯广告。本人所有文章仅在CSDN、掘金和个人博客(一定是异想之旅域名)发布,除此之外全部是盗文!
本文主要讲解的是自己注册了一个 E5 开发者账号,希望可以将 E5 的 OneDrive 作为网站的文件存储。如果你是希望用户登录自己的 Microsoft 账号后你获取他们自己账号的 OneDrive 文件,那么本文仅作实现参考。
这大概是本人写起来最累的一篇文章,因为我是在研究完这个东西、网站都上线了半年后才开始的。写这篇文章的初衷是真的不希望看到大家被微软晦涩难懂的文档劝退,因此内容可能不全面或出现错误,但保证可用。
事实上,Cloudreve 的添加存储策略页面也在很大程度上帮助了我的研究
特别说明的是,整个过程虽然理论上可以流畅完成,但如果遇到问题可以尝试给自己的网络施一点魔法。
创建应用
首先打开这个网页,登录自己将用于存储文件的Microsoft账号(应为你的E5中某个启用了OneDrive的账号,而非个人账号):
https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview
如果是世纪互联版本,则链接为
https://portal.azure.cn/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview
世纪互联账号未测试,不保证本文方法可用
登录后打开的页面应该是这样的,如果不是请重新点击上面的链接
选择左侧的“应用注册”,然后选择“新注册”:
在新打开的页面中,名称随意,受支持的帐户类型选择第三个 任何组织目录(任何 Azure AD 目录 - 多租户)中的帐户和个人 Microsoft 帐户(例如,Skype、Xbox)
,**重定向URI(可选)**选择 Web
,值填写 http://localhost/
点击“注册”按钮,加载完成后就进入了我们刚刚注册的应用程序主页。我们把应用程序(客户端) ID复制出来保存好。
点击“证书和密码”,再选择“新客户端密码”,截止期限根据自己需求选择(目前微软不提供永久有效的选项了,为防止频繁更新建议直接选到最长24个月),说明随意
然后我们就可以看到创建好的值和机密ID啦,请务必保存好,关闭页面后就无法再看到“值”了。
至此,我们的应用注册过程完成,现在手中获得了该应用程序的客户端ID以及客户端密码(刚刚那一步看到的“值”)
获取鉴权 Token
官方文档:https://learn.microsoft.com/zh-cn/onedrive/developer/rest-api/getting-started/graph-oauth?view=odsp-graph-online
官方已经说明了,这一个步骤有令牌流和代码流两种方式。前者虽然流程上更简单,但是获取到的 access_token 有效期仅8小时,到期后需要重新手动登录 Microsoft 账户才能继续调用 API(不考虑你想用爬虫操作Microsoft Login),这显然不适合我们的脚本。
构造并在浏览器中访问这个链接:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id={client_id}
&scope={scope}
&response_type=code
&redirect_uri=http://localhost/
其中,client_id
就是我们上一步获取的客户端ID,scope
指作用域,OneDrive官方的详细介绍在这里
建议和我一样填写 files.readwrite.all offline_access
!如果不一样,我不保证你后面的步骤顺利。
于是我构造出来的URI便是(client_id
中省略了最后一部分)
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=2dde8638-6489-4fc2-bfec-xxxxxxxxxxxx&scope=files.readwrite.all%20offline_access&response_type=code&redirect_uri=http://localhost/
在浏览器中访问之后,打开的是一个经典的 Microsoft 账号登录界面,正常登录我们要用来存储文件的账号即可
授权后会跳转到 localhost,此时这个网页应该会因为不存在打不开,不过没关系,我们只需要把地址栏中的内容复制出来,结构应该长这样:
http://localhost/
?code=xxxxx
&session_state=xxxxx#
此处我们记录 code
值,并构造这样的一个访问:
POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={client_id}&redirect_uri=http://localhost/&client_secret={client_secret}
&code={code}&grant_type=authorization_code
对应的 Python 代码:
from requests import *
r = post('https://login.microsoftonline.com/common/oauth2/v2.0/token',
data={
'client_id': '{client_id}',
'redirect_uri': 'http://localhost/',
'client_secret': '{client_secret}',
'code': '{code}',
'grant_type': 'authorization_code'
})
print(r.text)
其中需要替换的三个值我们在前面都已经得到了,不再赘述。
如果一群正常,你将会得到一个格式类似于下面这样的 JSON:
{
"token_type":"bearer",
"expires_in": 5126,
"ext_expires_in": 5126,
"scope":"wl.basic onedrive.readwrite",
"access_token":"EwCo...AA==",
"refresh_token":"eyJh...9323"
}
也可能略有不同,没关系,最重要的 access_token
和 refresh_token
有就行。
到这一步,我们就可以拿着 access_token
愉快地去调 API 了。但是如你所见,这个令牌是有有效期的,如果过期了之后,需要我们拿着 refresh_token
去兑换新的令牌
所以 refresh_token
是需要程序保存起来以便未来使用的,并且为了程序效率同时防止不必要的麻烦,建议把获取到每个 access_token
的时间也保存,并在每次发送请求前通过时间信息检测一下该令牌是否过期,不要等到 API 报错再去重新申请令牌
兑换新的 access_token
的请求格式如下
POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={client_id}&redirect_uri=http://localhost/&client_secret={client_secret}
&refresh_token={refresh_token}&grant_type=refresh_token
其响应格式还是一个和上面类似的 JSON,包含新的 access_token
和 refresh_token
上传文件
从此处开始,正常情况下,需要 access_token
的请求都应由服务端发出以保证安全。
OneDrive 提供两种上传模式,第一种是直接使用 access_token
鉴权并上传,这种方式不适合给用户使用且最大仅支持4MB文件,所以不再赘述,有需要可以自己去看。
此处我们介绍第二种,即先使用 access_token
获取一个上传会话,然后客户端拿到这个上传会话后可以直接免鉴权上传。
构造如下的请求:
POST https://graph.microsoft.com/v1.0/me/drive/root:{path}:/createUploadSession
Authorization: Bearer {access_token}
Content-Type: application/json
{
"item": {
"@microsoft.graph.conflictBehavior": "rename"
}
}
此处需要替换 path
和 access_token
两个参数,后者老生常谈了,前者就是我们文件在 OneDrive 上的存储路径,注意一下最前面需要有一个斜杠,后面不用,例如 /test.txt
更多可以在 body 中指定的参数详见官方文档,此处我们指定的
"@microsoft.graph.conflictBehavior": "rename"
是指如果存在同名文件则为新上传的文件重命名。
示例 Python:
path = '/test.txt'
access_token = 'xxxxxx'
r = post(
f'https://graph.microsoft.com/v1.0/me/drive/root:{path}:/createUploadSession',
headers={
'Authorization': 'Bearer ' + access_token,
'Content-Type': 'application/json'
},
data=json.dumps({
'item': {
'@microsoft.graph.conflictBehavior': 'rename',
},
}),
)
print(r.json())
正常情况下你会收到这样的返回:
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.uploadSession",
"expirationDateTime": "2023-03-25T07:41:03.482Z",
"nextExpectedRanges": [
"0-"
],
"uploadUrl": "https://yxzl-my.sharepoint.com/..."
}
接下来的东西应该看官方文档就能懂了,大家自行阅读一下。刚刚在JSON中获取到的 uploadUrl
就是文档中所说的 在 createUploadSession 响应中收到的 uploadUrl 值
。
为了防止大家眼大露神,把文档中很重要的一句话贴过来:
注意:如果应用将一个文件拆分为多个字节范围,则每个字节范围的大小必须是 320 KiB(327,680 个字节)的倍数。 如果使用的片断大小不能被 320 KiB 整除,会导致在提交某些文件时出错。
此处给出 Python 客户端上传文件的代码示例:
from requests import *
file = open('D:/Desktop/1.txt', 'rb').read()
length = len(file)
r = put(
'https://yxzl-my.sharepoint.com/...',
data=file,
headers={
'Content-Length': f'{length}',
'Content-Range': f'bytes 0-{length - 1}/{length}'
})
print(r)
print(r.text)
JavaScript Axios 的上传示例:
import { Axios } from "axios";
const uploadFile = async (file, uploadUrl) => {
/*
file: 要上传的文件对象
uploadUrl: 获取的上传会话
*/
const size = file.size; // 文件大小
const piece = 1024 * 1024 * 10; // 分片大小
let start = 0; // 当前分片的起始字节
let end = Math.min(piece, size); // 当前分片的结束字节
let cnt = Math.ceil(size / piece); // 分片数
while (start < size - 1) {
await Axios.put(uploadUrl, this.uploadFile.slice(start, end), {
headers: {
"Content-Range": `bytes ${start}-${end - 1}/${size}`,
},
});
cnt--;
if (cnt === 0) {
alert("上传成功");
return;
}
start = end;
end = Math.min(start + piece, size);
}
};
上传成功的返回如下(如果分片则是最后一个分片的返回),不过一点用也没有
{
"@odata.context": "https://yxzl-my.sharepoint.com/.../$metadata#items/$entity",
"@content.downloadUrl": "https://yxzl-my.sharepoint.com/.../download.aspx?UniqueId=...",
"createdBy": {
"application": {
"id": "...",
"displayName": "OneDriveTest"
},
"user": {
"email": "yixiangzhilv@yxzl.onmicrosoft.com",
"id": "...",
"displayName": "王 子涵"
}
},
"createdDateTime": "2023-03-25T07:26:03Z",
"eTag": "\"{F51D59DC-4D2F-466F-9CF0-E9895FF154C2},3\"",
"id": "...",
"lastModifiedBy": {
"application": ...,
"user": ...
},
"lastModifiedDateTime": "2023-03-25T07:39:52Z",
"name": "a.txt",
"parentReference": {
"driveType": "business",
"driveId": "b!...",
"id": "...",
"path": "/drive/root:"
},
"webUrl": "https://yxzl-my.sharepoint.com/.../a.txt",
"cTag": "\"c:{F51D59DC-4D2F-466F-9CF0-E9895FF154C2},2\"",
"file": {
"hashes": {
"quickXorHash": "..."
},
"irmEffectivelyEnabled": false,
"irmEnabled": false,
"mimeType": "text/plain"
},
"fileSystemInfo": {
"createdDateTime": "2023-03-25T07:26:03Z",
"lastModifiedDateTime": "2023-03-25T07:39:52Z"
},
"size": 150
}
你要说他有用的话,可能那个 donwloadUrl
稍微有点,但是那玩意有效期也不长
获取文件下载链接
这个就很简单了,此处介绍的方法是先获取一个文件的详细信息,再通过返回的 @microsoft.graph.downloadUrl
来下载。
构造请求:
GET https://graph.microsoft.com/v1.0/me/drive/items/root:{path}:',
Authorization: Bearer {access_token}
此处的 path
和上面创建上传会话的时候使用的应一致(前面都要有斜杠),access_token
不必再说了。
这个请求返回的是一个巨长的 JSON,把这个文件几乎所有的信息全给你了,但是我们只需要获取到 @microsoft.graph.downloadUrl
这一项即可。其值是一个 URL,访问这个 URL 可以免鉴权下载文件,但是有效期只有一小时。
参考链接:
- 获取文件或文件夹 - OneDrive API - OneDrive dev center | Microsoft Learn
- DriveItem - OneDrive API - OneDrive dev center | Microsoft Learn(这个是有关返回的 JSON 各项值的说明)