折腾了好一段时间,office 365、outlook 等的授权验证方式过于麻烦,因此记录开发步骤。
大概开发过程如下:
1,去注册 Azure 账号,然后进行应用注册。
2,租户邀请该用户加入组织中。
3,使用应用通过 OAuth 2.0 链接,获取用户的授权。
4,获取用户的 access_token 访问用户信息、替用户发送邮件等。
但是这些过程并不是一帆风顺,有很多曲折。比如不能跨租户访问等,必须先邀请用户加入组织。
否则会报错:
AADSTS50020: User account 'xxx@xxx.onmicrosoft.com' from identity provider 'https://sts.windows.net/--------/' does not exist in tenant 'Default Directory' and cannot access the application '---------'(My Apps) in that tenant. The account needs to be added as an external user in the tenant first. Sign out and sign in again with a different Azure Active Directory user account.
应用注册
打开:
https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
如果没有账号可以先注册登录,注册 AAD 不需要开通 Azure 订阅。
然后新注册一个应用。
-
“受支持的账号类型” 要选支持任意组织目录和个人。
-
选择 web 应用。
-
重定向应用设置 web 服务回跳地址。
注册应用完成后,可以去 “身份验证” 把这些功能都开启一些,没有什么特殊作用,现在开启,免得以后要用时重新配置。
然后创建客户端密码(即密钥),用于 OAuth2.0 访问授权、获取 access_token。
密钥是有有效期的。也可以使用证书,更加安全。
请在创建的时候保存密钥,因为除了第一次可以看到密钥,之后不能再次查看密钥!
然后到 API 权限中添加权限。
勾选以下四个即可。
关于应用的类型,有以下区别。
- web 应用,通过 OAuth2.0 回跳获取 code,再自行获取 access_token,需要设置回跳地址。
- 客户端类型,需要先向微软服务器请求,生成设备号,然后要求填写设备号即可。
本文只说 Web 应用。
跨租户或来宾访问
这一步是必须的。
因为注册的应用只能访问当前租户/组织内的组织用户、个人用户,无法访问其他个人用户、或其他租户内的用户。
因此,必须要邀请用户加入到自己的组织!
这一步要使用的是 Microsoft Entra ID,访问地址:
https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Overview
点击 “用户”。
邀请外部用户。
身份设置为来宾即可。
然后用户会收到一份电子邮件邀请。
如果不想让用户这么麻烦,你可以在提交邀请后,直接告诉用户打开:https://myapplications.microsoft.com/?tenantid={租户id}
即可。
租户 id 很容易找到。 比如在应用的属性界面、其他组织管理界面。一般情况下,租户id、目录id、组织id,是相同的意思。
但是对应要开发的应用来说,这样太麻烦了。
可以通过 https://learn.microsoft.com/zh-cn/graph/api/invitation-post?view=graph-rest-1.0&tabs=http 文档说明进行邀请。
如果租户上禁用了 B2B 邀请,或者 B2B 邀请仅限于管理员,则应用程序权限 (仅限应用) 不起作用。
这一点请看文章后面的 B2B 一节。
为了能够通过 API 访问租户的 API,通过 API 请求邀请用户,你需要写一套对接 Microsoft Entra ID 的代码。下一个小节介绍。
请注意,这里跟后面 outlook 授权用户的是不一样的!
API 邀请外部用户
有两种方式通过 API 授权访问 Microsoft Entra ID 。
因为我们要开发程序,因此要使用 “只应用访问”,使用应用方式获取访问权限,去请求租户中的 API,不可能通过用户访问授权操作 API。
文档地址:
https://learn.microsoft.com/zh-cn/graph/auth/auth-concepts
https://learn.microsoft.com/zh-cn/graph/auth-v2-service?tabs=http
若要使应用使用客户端凭据流获取对 Microsoft Graph 的授权和访问权限,必须执行以下五个步骤:
- 使用 Microsoft Entra ID 注册应用。
- 对应用配置 Microsoft Graph 应用程序权限。
- 请求管理员同意。
- 请求访问令牌。
- 使用访问令牌调用 Microsoft Graph。
打开 应用注册,找到文章开头注册的应用。
添加新的 API 权限。
包括:User.Invite.All
添加 API 权限之后,如果提示需要管理员同意,则点击上方的同意即可。
权限参考:https://learn.microsoft.com/zh-cn/graph/permissions-reference
如果在后台已经将 API 权限添加到列表中,那么 OAuth2.0 获取 access_token 时,scope 只需要填写
https://graph.microsoft.com/.default
即可。
然后,使用应用id 和密钥即可直接获取 access_token。
请求地址:
https://login.microsoftonline.com/{租户id}/oauth2/v2.0/token
请求方式:
application/x-www-form-urlencoded
请求参数:
client_id:11111111111111111111111111111
scope:https://graph.microsoft.com/.default
client_secret:11111111111111111111111111111
grant_type:client_credentials
scope
、grant_type
值固定即可。
![1699326540817](d:\Users\BSI\Documents\WeChat Files\wxid_nxurvoudbu5322\FileStorage\Temp\1699326540817.png)
此接口不需要获取 refresh_token,只要 token 过期,直接重新获取就行,不需要刷新 access_token。
拿到 access_token 后,请求接口邀请用户:
https://graph.microsoft.com/v1.0/invitations
接口请求类型是: application/json
。
Body:
{
"invitedUserEmailAddress": "aaa@aaa.onmicrosoft.com",
"inviteRedirectUrl": "http://localhost:5173",
"invitedUserType": "Guest",
"sendInvitationMessage": true
}
Authorization
填写前面获取到的 access_token。
invitedUserType
要填Member
不要使用Guest
,否则即使获得用户授权后,依然不能替代用户发送邮件。但是 User.Invite.All 权限只能Invite guest users to the organization
。属性说明如下:
属性 类型 必填 说明 invitedUserDisplayName String 否 被邀请的用户的显示名称。 invitedUserEmailAddress String 是 被邀请的用户的电子邮件地址。 不能使用 ~、!、$ 等特殊字符。 invitedUserMessageInfo invitedUserMessageInfo 否 包括自定义邮件文本、语言和抄送收件人列表。 invitedUserType String 否 被邀请的用户的 userType,默认情况下, 来宾为 Guest
,成员为Member
。inviteRedirectUrl String 是 兑换邀请后,用户应重定向到的 URL。 inviteRedeemUrl String 否 用户可用于兑换邀请的 URL。 resetRedemption 布尔值 否 重置用户的兑换状态并重新邀请用户,同时保留其用户标识符、组成员身份和应用分配。 此属性允许用户使用与上一个邀请中的电子邮件地址不同的电子邮件地址登录。 有关使用此属性的详细信息,请参阅 重置来宾用户的兑换状态。 sendInvitationMessage Boolean 否 指示电子邮件是否应发送至邀请的用户。 默认值为 false
。status String 否 邀请的状态。 可能的值为: PendingAcceptance
、Completed
、InProgress
和Error
。
发送添加用户请求后,在后台的用户列表可以看到此用户。
然后此用户打开 https://myapplications.microsoft.com/?tenantid={租户id}
即可授权应用,或者配置发送邮件,用户点击邮件即可访问。
你可以进一步使用 API 判断此邮件是否已经加入了组织。
如果你想一次性搞完,可以把 inviteRedirectUrl 地址设置成获取用户授权的调整地址。这样当用户加入组织后,立即跳转到用户授权界面,一步到位。
获取用户授权
此步骤前提是,该用户已经接受邀请,并被加入到应用所在的租户/组织中。
此步骤,是标准的 OAuth2.0 用户授权流程,如果是 Web应用,必须设置回调地址。
首先,构建授权的 URL 地址:
https://login.microsoftonline.com/{租户id}/oauth2/v2.0/authorize?client_id={应用的id}&response_type=code&redirect_uri={回调的地址}&response_mode=query&scope=https%3A%2F%2Fgraph.microsoft.com%2FMail.Send&state=1234
-
回调的地址需要使用 URL 编码,如
http://localhost:5173
编码成http%3A%2F%2Flocalhost%3A5173
。 -
scope 也需要 URL 编码,里面的权限项使用空格分隔,注意空格也是 URL 编码。scope 名称可以从 Microsoft Graph rest api 文档中找到。
地址:https://learn.microsoft.com/zh-cn/graph/overview?view=graph-rest-1.0
-
state 是客户端自己定义的,可以填写时间戳、guid 等标记,用于分辨是哪个用户点击的授权等。
用户打开该 url 后并授权后,会调整到回跳地址,地址中携带 code、state 等参数。
回跳地址携带了参数:
第二步,后台使用 code 获取该用户的 access_token 。
请求地址:
https://login.microsoftonline.com/{租户id}/oauth2/v2.0/token
Body 类型 : application/x-www-form-urlencoded
。
client_id:11
scope:https://graph.microsoft.com/mail.Send
redirect_uri:http://localhost:5173
code:....
redirect_uri:http://localhost:5173
client_secret:...
grant_type:authorization_code
获取到 access_token,后台可以通过 refresh_token 去定时刷新用户的 access_token,防止 access_token 失效后用户需要重新授权。
HTTP/1.1 200 OK
Content-type: application/json
{
"token_type": "Bearer",
"scope": "Mail.Read User.Read",
"expires_in": 3736,
"ext_expires_in": 3736,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...",
"refresh_token": "AwABAAAAvPM1KaPlrEqdFSBzjqfTGAMxZGUTdM0t4B4..."
}
关于 socpe ,这里要说明一下。如果想获取 access_token 时,返回 refresh_token,则需要使用
openid offline_access https://graph.microsoft.com/mail.Send
这样的格式。前缀使用openid offline_access
。如果不设置这个前缀,那么是拿不到 refresh_token 的。
接下来,可以使用 API 替代用户,请求 outlook 中各个接口。这些接口统一属于 Microsoft Graph 。
Microsoft Graph 是 Microsoft 365 中通往数据和智能的网关。 使用 Microsoft Graph 生成智能应用、获取见解和分析,并扩展 Microsoft 365 体验。
要使用这些接口,需要开通不同的权限。
B2B 外部标识
一般不需要设置,主要是用于处理租户外部用户如何加入和访问此组织的资源。
不能访问用户 API
请求后报错:
{
"error": {
"code": "MailboxNotEnabledForRESTAPI",
"message": "The mailbox is either inactive, soft-deleted, or is hosted on-premise."
}
}
文档说明地址:
https://learn.microsoft.com/zh-cn/graph/outlook-send-mail-from-other-user
这是因为部分接口不能跨租户使用,因此申请获取用户授权时以及获取 access_token 时,不能使用租户id获取,而是使用 common。
获取授权的地址改成:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={客户端id}&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A5173&response_mode=query&scope=openid%20offline_access%20email%20Mail.ReadWrite%20Mail.Send.Shared&state=1234
获取 access_token 的地址改成:
https://login.microsoftonline.com/common/oauth2/v2.0/token
然后使用获取到的 access_token 即可访问各种 api。
如果其他接口都可以使用,只有发送邮件接口不行:
Remote server returned '550 5.7.708 Service unavailable. Access denied, traffic not accepted from this IP. For more information please go to http://go.microsoft.com/fwlink/?LinkId=526653 AS(7230)
文章评论
我想问一下,如果改成common节点后跑发邮件的API提示这个错误要怎么解决OrganizationFromTenantGuidNotFound","message":"The tenant for tenant guid
@bbb 就是因为 common 搞不了才换了方式,common 好像要企业成员,而不是邀请加入。
@痴者工良 我现在用Microsoft365里面的SMTP接口过了SMTP服务的身份验证,也能正常代发邮件了,但是只能租户的创立者账户邮箱才能过Oauth2认证,所有邀请的外来来宾都过不了,是我还有什么权限没授予吗
@bbb 来宾帐号都用不了 common 的接口😂
确实是,搞了好几天都看不明白,结果居然还有一个 common 的 oath2 端点,通过这个端点使用 graph api 邮件的收发都走通了,但是 通过 smtp/imap 的还是不行
@mrcode 点个赞
@mrcode scope 换成 https://outlook.office.com/IMAP.AccessAsUser.All
@mrcode 怎么弄的可以教一下不
@bbb restapi 授权码获取尝试:https://www.yuque.com/mrcode.cn/note-combat/iv6gmsr1qzsk4x06
Outlook 邮箱 Graph API 收发全流程 https://www.yuque.com/mrcode.cn/note-combat/lvgib3v346szknn9
准备给 logseq 开发一个同步 Microsoft ToDo 的插件,结果搞了两晚上没搞懂 Microsoft graph 的授权验证,于是搁置了
@b-yp 那现在搞好了么