关于微软的账号及服务
下面地址用于登录网页版outlook邮箱。
https://outlook.live.com/
下面两个地址登录后是相同的页面,仅仅是url不一样。
https://www.microsoft365.com/
https://www.office.com/
下面是微软账号登录页面,登录后并不能跳转到Azure页面,但可以跳转到www.office.com。
https://account.microsoft.com/
下面是属于某个组织的账号登录页面。普通的个人账号是不能登的。之前使用另一个邮箱生成了E5免费订阅
https://myaccount.microsoft.com/
下面是组织的管理员账号登录页面。普通MSA账号是不能登的。 登录后title为 Microsoft 365 admin center。通过 Admin centers -> Identity 可以跳转到 Microsoft Entra admin center
https://admin.microsoft.com/
开通Azure之前,必须有个Microsoft账号,这是必须的。 然后就是激活Azure,会得到一些免费的服务的额度,有些服务是永久免费的,也可以购买一些订阅。
下面是Azure登录网址,可以跳转到https://entra.microsoft.com/,但不能跳转到 www.microsoft365.com/或 www.office.com/
https://portal.azure.com/
在Azure页面进入Microsoft Entra ID部分后,可以看到很多设置内容与直接登录 https://entra.microsoft.com/ 页面看到的内容是相同的。
下面是登录Microsoft Entra Admin Center的网址。登录后title为 Microsoft Entra admin center
https://entra.microsoft.com/
我的个人邮箱账号,其实就是outlook邮箱地址,它不属于任何组织或学校,所以就是个人账号,Microsoft Account (MSA),它可以登录以下网址:
https://www.microsoft365.com/
https://www.office.com/
https://account.microsoft.com/
https://portal.azure.com/ (登录后,在All Users里只有自己一个user)
https://entra.microsoft.com/ (登录后,在All Users里只有自己一个user)
它不能登录如下网址:
https://myaccount.microsoft.com/ (只能是组织的成员)
https://admin.microsoft.com/ (只能是组织里的管理员)
Microsoft 365 E5: 是 Microsoft 365 企业版套件中最高级的订阅计划。它提供了全面的生产力、高级安全性、语音功能和分析工具。在全球云上可用,国内的21世纪互联云上不可用。
Microsoft 365 开发者计划(E5 沙盒):这是免费开发者订阅模型。它提供了一个 E5 级别的沙盒环境,专门用于开发和测试目的。
当申请 Microsoft 365 E5 开发者订阅时,系统会自动创建一个包含多个用户的测试环境。这些用户是预先设置的模拟用户,目的是让开发者可以测试各种多用户场景,而无需手动创建每个用户。而我则是这个环境或者说组织或租户的管理员。
使用 E5 开发者计划账号是可以激活 Azure 的。Microsoft 365 开发者计划通常包括 Azure 订阅,这允许开发者在 Azure 环境中进行开发和测试。
登录 Azure 时,能够看到与 Microsoft 365 E5 开发者环境相关联的用户。这是因为 Azure Active Directory (Azure AD) 是 Microsoft 365 和 Azure 共享的身份管理服务。
在 Azure 中,仍然保持管理员身份。很可能是 Azure AD 中的全局管理员,与在 Microsoft 365 E5 开发者环境中的角色一致。
在 Microsoft 365 管理中心看到的那些模拟用户也会出现在 Azure AD 中。这些用户账户是跨 Microsoft 云服务共享的。
Microsoft Entra: 是微软推出的身份和访问管理解决方案的品牌,旨在帮助组织更好地管理用户身份、访问权限和安全性。随着数字化转型和远程工作的普及,企业对身份管理和安全性的需求愈加迫切,因此微软将其身份管理相关的服务整合成了 Microsoft Entra 套件。
Microsoft Entra ID: 即原Azure Active Directory (Azure AD)。
Azure Active Directory (AAD): 是微软提供的云端身份和访问管理服务。它帮助组织管理用户、应用程序和设备的身份验证和授权。AAD 是企业使用微软云服务(如 Microsoft 365、Azure、Dynamics 365)时的核心身份服务, 现已成为Microsoft Entra的一部分。
Microsoft Entra Admin Center: 是一个集成管理门户,用于管理 Azure Active Directory (Azure AD) 及其相关身份和访问管理服务。它是微软的 Entra 产品系列的一部分,专注于统一身份验证和访问控制,以帮助组织在云端和本地环境中管理其身份架构。
Microsoft Identity Platform: 是微软提供的一套用于身份验证和授权的开发工具和服务,专注于帮助开发者安全地处理用户身份,并管理应用程序对资源的访问。该平台是现代身份管理的基础,特别是在与 Azure Active Directory (Azure AD) 集成时,支持企业级和消费者级的应用程序。
Microsoft Authentication Library: MSAL 是一个客户端库,用于简化身份验证和获取访问令牌的过程。MSAL 支持多个平台,包括 .NET、JavaScript、Java、Python 和移动平台(如 iOS 和 Android)。
通过 MSAL,开发者可以实现 OAuth 2.0 和 OpenID Connect 协议的工作流,简化用户登录、令牌管理和刷新令牌等操作。
Microsoft Graph: 是一个由微软提供的 RESTful API 平台,它允许开发者访问微软云服务中的丰富数据和资源。通过 Microsoft Graph,开发者可以与多个 Microsoft 365 服务(如 Outlook、OneDrive、Teams、Azure Active Directory 等)进行交互,从而在应用程序中集成这些服务并访问企业级数据。
目前微软好像已经关闭了传统的通过pop3, smtp, imap等方式访问其outlook邮件的方式, outlook客户端现在好像只能使用exchange来连接邮件服务器。 这导致使用编程方式访问微软的邮件,以及像onedrive等等一些云上的资源时,不得不采用现在微软支持的方式,即先获取访问token,再调用 Microsoft Graph API 的方式来访问。而微软的token只会授权给其信任的应用,即该应用必须在 Microsoft Entra 上注册,会生成 client ID 信息。 程序需要使用该注册好的app来获取token。
如下内容以微软官方文档做为参考资料: https://learn.microsoft.com/en-us/entra/msal/python/getting-started
获取token的两种Client
public client: 公共客户端,不设单独密码,由用户通过前端交互页面进行登录的方式来验证身份,代表一个具体用户。一般用在桌面应用或其它设备应用上。
confidential client: 机密客户端,设置密码或证书,可以代表应用程序自身来登录。一般用在需要以无交互的方式来获取access token的场景中,比如后台服务的守护进程,web api等。
使用MSAL库来获取access token
静默获取 与 交互式获取: 这里的交互式不代表一定要有交互操作,而是指要走一遍获取token的流程。 而静默式就直接从缓存中获取token。
- Interactive:一般通过浏览器完成登录的身份验证过程。像desktop类型的客户端,需设置其redirect url为 http://localhost,或者安装broker. 如果是机密客户端,可以以无交互操作的方式获取token。
- silent: 一般用于该账号最近登录过,缓存里还有其access token或refresh token。不用再次走一遍获取token的流程。
Public clients interactive token acquisition(公共客户端获取token流程) 有如下三种方式:
- Device code flow: 用于那种不带浏览器的设备
- Acquire token interactive: 一般的用于使用浏览器来登录账号以验证身份并获取token的
- Username and password: 这种方式直接把账号和密码写在程序代码里,不安全,不推荐,并且非组织账户(即个人MSA账号)不支持。
Confidential clients interactive token acquisition(机密客户端获取token流程) 有以下几种方式:
- Acquire token for client: 客户端以自己的身份而不是代表一个用户来获取token,这种需要提供app的secret,无页面交互操作。
- Acquire token on behalf of: 客户端代表一个用户来获取token,这种机制的具体步骤还没搞清楚。
- Acquire token by authorization code flow: 这种机制也没搞懂,不知道是否需要前端交互操作。
public client里配置的api permission要选择dedicated类型,而confidential client要选择application类型。
Public client + 用户密码获取token flow,在我测试时一直没成功,总是提醒multi factor authentication未关闭,但我在能关闭的地方都进行了关闭。
以下是测试能运行的方案:
public client + Acquire token interactive。
MSA账号不需要applicaiton里设置任何的redirect url。
import requests
import msal
# 配置
CLIENT_ID = 'df13d537-xxxxxxxxxxx-d41bacfb56fc'
TENANT_ID = '14584901-xxxxxxxxxxxx-90cc90e98d99'
# 初始化MSAL应用
# authority, 身份验证的授权端点。/consumers: 个人账户, /{TENANT_ID}: 织织账户, /common: 多租户应用. 不设置此参数时,默认为/common
app = msal.PublicClientApplication(
CLIENT_ID,
authority=f'https://login.microsoftonline.com/consumers',
)
# 获取令牌
token_response = app.acquire_token_interactive(
scopes=["Mail.ReadWrite", "Mail.Send"]
)
if "access_token" in token_response:
access_token = token_response['access_token']
print('access_token is ', access_token)
# 使用access token 读取和发送邮件
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
# 例如:读取邮件
graph_api_endpoint = 'https://graph.microsoft.com/v1.0/me/messages?$select=sender,subject'
response = requests.get(graph_api_endpoint, headers=headers)
if response.status_code == 200:
emails = response.json()
print(emails)
else:
print(f'Error: {response.status_code}')
else:
print(f"Error acquiring token: {token_response.get('error_description')}")
使用组织账号创建的public client,需要在application设置里手动填加一个 http://localhost。
import requests
import msal
# 配置
CLIENT_ID = 'a7eddc56-xxxxxxxxxxxxxx-8566a9725796'
TENANT_ID = 'ae2ca951xxxxxxxxxxxxxx-3f06e6e22523'
# 初始化MSAL应用
# authority, 身份验证的授权端点。/consumers: 个人账户, /{TENANT_ID}: 织织账户, /common: 多租户应用. 不设置此参数时,默认为/common
app = msal.PublicClientApplication(
CLIENT_ID,
authority=f'https://login.microsoftonline.com/{TENANT_ID}',
)
# 获取令牌
token_response = app.acquire_token_interactive(
scopes=["Mail.ReadWrite", "Mail.Send"]
)
if "access_token" in token_response:
access_token = token_response['access_token']
print('access_token is ', access_token)
# 使用access token 读取和发送邮件
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
# 例如:读取邮件
graph_api_endpoint = 'https://graph.microsoft.com/v1.0/me/messages?$select=sender,subject'
response = requests.get(graph_api_endpoint, headers=headers)
if response.status_code == 200:
emails = response.json()
print(emails)
else:
print(f'Error: {response.status_code}')
else:
print(f"Error acquiring token: {token_response.get('error_description')}")
使用机密客户端. application里不需要设置redirect url
import msal
import requests
# client_secrets:
CLIENT_ID = '507be114-xxxxxxxxxxxxxxxx-a78dc4fbadff'
TENANT_ID = 'ae2ca951-xxxxxxxxxxxxxxxx-3f06e6e22523'
SECRET_VALUE = '.wM8Q~xxxxxxxxxxxxxxxxxxxxxxxx-MaWH'
# userPrincipalName = 'Roland@6xtfh5.onmicrosoft.com'
userPrincipalName = 'AdeleV@6xtfh5.onmicrosoft.com'
app = msal.ConfidentialClientApplication(
CLIENT_ID,
authority=f'https://login.microsoftonline.com/{TENANT_ID}',
client_credential=SECRET_VALUE,
)
# Scopes 需要使用完整的 URL
scopes = [
'https://graph.microsoft.com/.default' # 对于客户端凭证流,使用 .default scope
]
token_response = app.acquire_token_for_client(scopes=scopes)
print(token_response)
if "access_token" in token_response:
access_token = token_response['access_token']
print('access_token is ', access_token)
# 使用access token 读取和发送邮件
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
# 例如:读取邮件
graph_api_endpoint = f'https://graph.microsoft.com/v1.0/users/{userPrincipalName}/messages?$select=sender,subject'
response = requests.get(graph_api_endpoint, headers=headers)
if response.status_code == 200:
emails = response.json()
print(emails)
else:
print(f'Error: {response.text}')
else:
print(f"Error acquiring token: {token_response.get('error_description')}")
python运行msal来获取token时,会在内存中产生一个缓存,但程序运行结束后,缓存也就释放了。 可能通过实现将缓存保存在硬盘上,这样下次程序在获取token时,可以先尝试从缓存中获取,如果获取失败(比如token过期,或者没有token),再通过正常的步骤获取token,否则,可以直接从缓存中获取token,使用acquire_token_silent方法。如果access token过期了,还会自动使用refresh token去获取一个新的access token。
需要注意的是,尽管我们会发现交互式身份验证有时候不需要输入账号密码就会直接显示 Authentication completed. You can close this window now. 但这不代表token的缓存是在浏览器的cookies中。 msal的token并不是由浏览器来保管。但当我们在浏览器中登录账号后,浏览器中会保留相关cookies,以便我们省去输入密码的步骤。 身份认证的过程是借助于浏览器的。在初次我们获取token被提示要在浏览器中输入密码后,浏览器就帮我们缓存账号密码了,下次就不必再输入密码了。
使用持久化缓存的方式,减少身份认证时输入密码的次数
access token有效期默认是1小时,refresh token在single page apps程序中是24小时,其他程序是90天。 并且这两个时长好像没办法设置及修改。
import os
import msal
import requests
# 个人账户:'Roland.He@outlook.com' app: auto_email
ACCOUNT_TYPE = 'MSA'
CLIENT_TYPE = 'Public'
CLIENT_ID = 'df13d537-xxxxxxxxxxxxxx-d41bacfb56fc'
TENANT_ID = '14584901-xxxxxxxxxxxxxx-90cc90e98d99'
# 组织账号: 'Roland@6xtfh5.onmicrosoft.com' app: myapp
# ACCOUNT_TYPE = 'ORG'
# CLIENT_TYPE = 'Public'
# CLIENT_ID = 'a7eddc56-xxxxxxxxxxxxxx-8566a9725796'
# TENANT_ID = 'ae2ca951-xxxxxxxxxxxxxx-3f06e6e22523'
userPrincipalName = None # 只在Credential Clent会用到
SECRET_VALUE = None # 只在Credential Clent会用到
# 组织账号: 'Roland@6xtfh5.onmicrosoft.com' app: MyCredentialApp
# ACCOUNT_TYPE = 'ORG'
# CLIENT_TYPE = 'Credential'
# CLIENT_ID = '507be114-xxxxxxxxxxxxxx-a78dc4fbadff'
# TENANT_ID = 'ae2ca951-xxxxxxxxxxxxxx-3f06e6e22523'
# SECRET_VALUE = '.wM8Q~xxxxxxxxxxxxxx-MaWH'
# userPrincipalName = 'Roland@6xtfh5.onmicrosoft.com'
cache_file_path = "token_cache.json"
class FileTokenCache:
def __init__(self, cache_file_path):
self.cache_file_path = cache_file_path
self.cache = msal.SerializableTokenCache()
if os.path.exists(self.cache_file_path):
with open(self.cache_file_path, 'r') as cache_file:
self.cache.deserialize(cache_file.read())
def __enter__(self):
return self.cache
def __exit__(self, exc_type, exc_val, exc_tb):
if self.cache.has_state_changed:
with open(self.cache_file_path, 'w') as cache_file:
cache_file.write(self.cache.serialize())
if ACCOUNT_TYPE == 'MSA':
authority = 'https://login.microsoftonline.com/consumers'
else:
authority = f"https://login.microsoftonline.com/{TENANT_ID}"
cache = msal.SerializableTokenCache()
if os.path.exists(cache_file_path):
with open(cache_file_path, 'r') as cache_file:
cache.deserialize(cache_file.read())
if CLIENT_TYPE == 'Public':
scopes=["Mail.ReadWrite", "Mail.Send"]
graph_api_endpoint = 'https://graph.microsoft.com/v1.0/me/messages?$select=sender,subject'
# with FileTokenCache(cache_file_path) as cache:
app = msal.PublicClientApplication(
CLIENT_ID,
authority=authority,
token_cache=cache
)
else:
scopes = ['https://graph.microsoft.com/.default'] # 对于客户端凭证流,使用 .default scope
graph_api_endpoint = f'https://graph.microsoft.com/v1.0/users/{userPrincipalName}/messages?$select=sender,subject'
# with FileTokenCache(cache_file_path) as cache:
app = msal.ConfidentialClientApplication(
CLIENT_ID,
authority=authority,
client_credential=SECRET_VALUE,
token_cache=cache
)
# initialize result variable to hole the token response
token_response = None
# We now check the cache to see
# whether we already have some accounts that the end user already used to sign in before.
# 由于msal使用的缓存与浏览器的缓存是隔离的,msal的缓存实际在python程序执行时的内存中, 所以当程序下次运行时,它是找不到上次登录时的缓存的。
# 所以返回的accounts列表将永远是空的。除非手动实现将缓存序列化存储在硬盘中。
# 但有时候你可能会好奇,既然msal的缓存会随着程序执行完就消失,为什么我再次运行时,浏览器弹出的身份认证窗口里并不需要我再次输入密码呢?
# 这是因为身份登录的过程是在浏览器中进行的,这时浏览器自身的缓存就起到了简化登录的作用,如果意识到用户最近刚登录过,它就不再需要你提供密码了。
accounts = app.get_accounts()
print('accounts is ', accounts)
if accounts:
# If so, you could then somehow display these accounts and let end user choose
print("Pick the account you want to use to proceed:")
for a in accounts:
print(a["username"])
# Assuming the end user chose this one
chosen = accounts[0]
# Now let's try to find a token in cache for this account
token_response = app.acquire_token_silent(scopes, account=chosen)
if not token_response:
# So no suitable token exists in cache. Let's get a new one from Azure AD.
token_response = app.acquire_token_interactive(scopes=scopes)
if cache.has_state_changed:
with open(cache_file_path, 'w') as cache_file:
cache_file.write(cache.serialize())
if "access_token" in token_response:
print(token_response["access_token"]) # Yay!
access_token = token_response["access_token"]
# 使用access token 读取和发送邮件
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
response = requests.get(graph_api_endpoint, headers=headers)
if response.status_code == 200:
emails = response.json()
print(emails)
else:
print(response.text)
else:
print(token_response.get("error"))
print(token_response.get("error_description"))
print(token_response.get("correlation_id")) # You may need this when reporting a bug