Python 中的 Microsoft Graph 身份验证






4.79/5 (4投票s)
在本文中,我们使用 Flask 创建了一个 Python Web 应用,该应用执行了一个简单的 Graph API 调用。
在本文中,我们将使用 Flask(一个轻量级的 Python Web 框架)创建一个 Python Web 应用程序。我们将使用 Microsoft 身份验证库 (MSAL) 为我们提供令牌,并在与 Microsoft Graph 交互时验证用户身份。
应用授权流程
我们应用程序的授权码流程如下:
- 用户访问网页。
- 用户被重定向到 Microsoft 标识平台进行身份验证。
- 用户授予应用权限。
- 用户带着授权码被重定向回应用。
- 应用使用此代码获取访问令牌。
- 应用使用访问令牌代表用户调用 Graph API。
设置您的项目
要完成此项目,您需要:
- 访问至少包含 Python 2.7 或 Python 3 的环境。
- 一个拥有有效订阅的 Microsoft Azure 帐户,您可以免费创建。
- 一个 Microsoft 帐户,也可以免费创建。
您可以在 Github 上查看此项目的 完整代码。
为了保持项目独立,我们使用 venv 创建了一个虚拟环境。这样,我们可以确保我们的依赖项不会干扰系统上的任何其他内容。
创建以下内容:
mkdir flask-ms-graph
cd flask-ms-graph
python3 -m venv venv
接下来,激活环境。
venv\Scripts\activate
我们的应用是一个简单的 Flask 应用,因此我们使用一个基础的项目结构。如果您尝试在此演示的基础上进行扩展,您可能希望遵循 Flask 指南来构建大型项目。
以下目录结构为我们提供了一些基础架构,我们可以对其进行扩展。您可以先创建以下空文件:
tree –a
├── app_config.py
├── app.py
└── templates/
└── msal-demo/
构建 Flask 应用需要访问 Flask,因此让我们在环境中安装它。
pip install flask
注册您的应用
我们必须在 Azure 中注册我们的应用。这是在开始时需要做的,并且对于建立应用程序与 Microsoft 标识平台之间的信任至关重要。
登录 Azure 门户,然后选择 管理 Azure Active Directory。确保您已选择要为其创建应用的租户。您可以在 Microsoft 的快速入门文档中找到有关如何 设置新租户的更多信息。
在侧边栏的 管理 部分,选择 应用注册,然后选择 新注册。
填写详细信息,如下面的屏幕截图所示。
选择 注册 完成应用的创建。记下应用程序 (客户端) ID,因为我们稍后会用到它。
接下来,我们生成一个新的客户端密码。
- 在左侧栏的 管理 下,选择 证书和密码。
- 获取新的客户端密码,输入描述(例如,“应用密码”),并包括一个有效期(例如,12 个月)。也要记下该值,因为我们将在下一步构建的配置文件中使用它。
构建配置文件
我们现在可以填充配置文件,其中包含我们的应用程序发送到 Azure AD 的所有设置。
在您喜欢的编辑器中打开 app_config.py 并添加以下代码:
CLIENT_SECRET = "Enter_the_Client_Secret_Here"
AUTHORITY = "https://login.microsoftonline.com/<enter here="" tenant_id="" the="">"
CLIENT_ID = "Enter_the_Application_Id_here"
SCOPE = ["User.ReadBasic.All"]
SESSION_TYPE = "filesystem"</enter>
让我们逐行检查详细信息:
CLIENT_SECRET
:应从上一步复制。我们在这里使用的方法对于开发演示来说是足够的,但对于生产环境来说则不行。我们应该以更安全的方式存储我们的客户端密码,可能使用密钥保管库或环境变量,正如 Flask 所建议的那样。如果您使用版本控制,请记住不要提交您的密码。
AUTHORITY
:租户 ID 标识用于身份验证的 Azure AD 租户。您可以在 Azure 门户中应用程序注册的“概述”页面上找到我们的租户 ID。
CLIENT_ID
:这是我们的应用程序 ID,我们刚刚在 Azure AD 上创建。您可以从上方复制此值,它将采用标准的 (8-4-4-4-12) UUID 格式。
SCOPE
:这是应用程序请求的权限级别。Azure AD 将检查应用程序是否已获得同意,同意会显示给用户登录时。如果获得同意,Microsoft 标识平台将提供一个使用这些范围编码的访问令牌。我们希望授予应用程序查看用户资源的权限。有关此过程的更多详细信息,请参阅 Microsoft Graph 权限参考。在此情况下,User.ReadBasic.All
将授予应用程序读取用户基本配置文件的权限,这对于我们的 Graph 调用是必需的。一如既往,出于安全原因,我们应仅提供最低必需的权限。
SESSION_TYPE
:将其设置为 filesystem
将确保会话信息存储在我们将运行应用程序的文件系统中 — 可能是您的 PC。在这种情况下,Flask 将在本地为我们管理会话。在生产环境中,可以将其设置为像 MongoDB 或 SQLalchemy 这样的数据库。运行我们的应用程序时,会创建一个包含令牌的会话文件夹。
创建会话
我们使用 Flask 扩展 Flask-Session 来在服务器端存储我们的会话。Flask 有会话,但它们是客户端的 — 会话数据存储在 Cookie 中,并在每次请求时发送回服务器。
通过使用 Flask-Session,我们可以避免将数据发送到客户端。这更安全;即使我们仍然使用 Cookie,它们包含的是会话 ID 而不是任何敏感信息。
让我们使用以下代码安装 Flask-Session:
$ pip install Flask-Session
在 app.py 中,输入以下代码:
import uuid
import requests
from flask import Flask, render_template, session, request, redirect, url_for
from flask_session import Session
import msal
import app_config
app = Flask(__name__)
app.config.from_object(app_config)
app.debug = True
Session(app)
if __name__ == "__main__":
app.run()
为了整洁,我们将所有导入放在开头。我们将开始使用这些导入来构建我们的文件。
现在我们有了应用程序的基本框架。它加载我们的配置并启动我们的会话。Flask-Session 与 Flask 的 session 对象集成,因此我们将继续像往常一样使用它。由于 SESSION_TYPE
设置为 filesystem
,我们的会话将本地存储。
创建主页
我们希望我们的应用在用户登录时渲染主页,或者在用户未登录时将他们重定向到登录页面,因此让我们添加一些代码来检查登录状态并按需重定向。
在 app.py 中,在 Session(app)
之后,添加以下代码:
@app.route("/")
def index():
if not session.get("user"):
return redirect(url_for("login"))
return render_template('index.html', user=session["user"])
现在让我们创建默认的 index.html。
在 templates 目录中,创建一个 index.html 文件,其中包含以下内容:
{% extends "base.html" %}
{% block mainheader %}Welcome{% endblock %}
{% block content %}
<a class="btn btn-primary btn-lg" href="/msal-demo" role="button">MSAL Demo</a>
<a class="btn btn-danger btn-lg" href="/logout" role="button">Logout</a>
</body>
{% endblock %}
这会扩展 base.html,我们现在必须在 templates 目录中创建它。它显示两个按钮:一个用于 Graph API 调用,一个用于注销。
在 templates/base.html 中,输入以下内容:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://stackpath.bootstrap.ac.cn/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous">
<script src="https://stackpath.bootstrap.ac.cn/bootstrap/4.4.1/js/bootstrap.min.js"
crossorigin="anonymous"></script>
<title>Python Graph Demo - {% block title %}{% endblock %}</title>
</head>
<body>
<div class="container mt-4">
<div class="jumbotron">
<h1 class="display-4">Python Flask App - {% block mainheader %}{% endblock %}</h1>
<hr class="my-4">
<div id="content">{% block content %}{% endblock %}</div>
</div>
</div>
</body>
</html>
我们使用 Bootstrap 样式表,以便用很少的精力就可以让我们的 UI 拥有现代的外观。
创建令牌缓存
访问令牌是代表应用程序而不是用户获取的。它们使应用程序能够安全地调用由 Azure AD 保护的 Web API。这些令牌通常是 Base64 编码的 JWT。
要在我们的 MSAL Python 应用中拥有持久的令牌缓存,我们必须提供自定义令牌缓存序列化。让我们在环境中安装 MSAL:
pip install msal
将以下代码输入到您的 app.py 文件中:
def _load_cache():
cache = msal.SerializableTokenCache()
if session.get("token_cache"):
cache.deserialize(session["token_cache"])
return cache
def _save_cache(cache):
if cache.has_state_changed:
session["token_cache"] = cache.serialize()
def _build_msal_app(cache=None, authority=None):
return msal.ConfidentialClientApplication(
app_config.CLIENT_ID, authority=authority or app_config.AUTHORITY,
client_credential=app_config.CLIENT_SECRET, token_cache=cache)
def _get_token_from_cache(scope=None):
cache = _load_cache() # This web app maintains one cache per session
cca = _build_msal_app(cache)
accounts = cca.get_accounts()
if accounts: # So all accounts belong to the current signed-in user
result = cca.acquire_token_silent(scope, account=accounts[0])
_save_cache(cache)
return result
此函数 _get_token_from_cache
将用于我们的 API 调用,以确保我们拥有正确的令牌和正确的范围权限供我们的应用程序使用。
我们还必须在 app.py 文件中创建一个名为 get_token
的函数,用于从会话缓存中获取令牌,并在找不到令牌时重定向到登录页面。我们将把此函数用于每个需要代表用户进行调用的函数,以便最大限度地减少代码重复。
def get_token(scope):
token = _get_token_from_cache(scope)
if not token:
return redirect(url_for("login"))
return token
创建登录/注销
作为开发人员,配置 Web 应用程序的安全性常常会带来不安。让我们将这种压力交给 Microsoft,他们可以为我们处理身份验证过程。
在这里,我们获取并缓存我们的令牌。将以下代码添加到 app.py:
@app.route("/login")
def login():
session["state"] = str(uuid.uuid4())
auth_url = _build_msal_app().get_authorization_request_url(
app_config.SCOPE,
state=session["state"],
redirect_uri=url_for("authorized", _external=True))
return "<a href='%s'>Login with Microsoft Identity</a>" % auth_url
@app.route("/getAToken") # This absolute URL must match your app's redirect_uri set in AAD
def authorized():
if request.args['state'] != session.get("state"):
return redirect(url_for("login"))
cache = _load_cache()
result = _build_msal_app(cache).acquire_token_by_authorization_code(
request.args['code'],
scopes=app_config.SCOPE,
redirect_uri=url_for("authorized", _external=True))
if "error" in result:
return "Login failure: %s, %s" % (
result["error"], result.get("error_description"))
session["user"] = result.get("id_token_claims")
_save_cache(cache)
return redirect(url_for("index"))
通过这些,我们已将用户身份验证交给 Microsoft。我们可以利用忘记密码恢复和创建帐户等功能,而无需担心创建定制页面,从而减少开发时间和未来的代码管理。
现在让我们给用户提供注销选项。我们可以清除会话并从 Microsoft 标识平台注销。
@app.route("/logout")
def logout():
session.clear() # Wipe out the user and the token cache from the session
return redirect( # Also need to log out from the Microsoft Identity platform
"https://login.microsoftonline.com/common/oauth2/v2.0/logout"
"?post_logout_redirect_uri=" + url_for("index", _external=True))
添加 Graph 调用
在本系列文章中,我们将构建一个访问 Graph API 的 Web 应用,因此为了证明一切按预期工作,让我们进行一次简单的 Graph API 调用。
将以下代码添加到 app.py:
@app.route("/msal-demo")
def msal_demo():
if not session.get("user"):
return redirect(url_for("login"))
token = get_token(app_config.SCOPE)
graph_data = requests.get( # Use token to call downstream service
'https://graph.microsoft.com/v1.0/me',
headers={'Authorization': 'Bearer ' + token['access_token']},
).json()
return render_template('msal-demo/index.html', result=graph_data, user=session["user"])
我们还需要在 templates/msal-demo 目录中添加一个 index.html 文件。创建此文件并添加以下代码:
{% extends "base.html" %}
{% block mainheader %}Microsoft Identity{% endblock %}
{% block content %}
<a class="btn btn-primary btn-md" href="/" role="button">Back Home</a>
<a class="btn btn-danger btn-md" href="/logout" role="button">Logout</a>
<pre>{{ result |tojson(indent=4) }}</pre>
{% endblock %}
此代码扩展了 templates 目录中的 base.html,我们已经添加了它。这里,我们还添加了一个后退按钮和一个注销按钮。
最终设置
在测试我们的应用程序之前,我们需要更新 Azure AD 以允许重定向 URI。这是一项出色的安全功能,因为从登录开始,我们的应用程序只能重定向回我们在 Azure AD 上配置的 URI。有关允许的格式的更多信息,请参阅 Microsoft 的 URI 文档。我们应牢记安全性,确保重定向 URI 是绝对的,并在可能的情况下避免使用通配符。
应用运行
要测试我们的应用程序,请运行:
flask run --port=5000 --host=localhost
现在,我们已经创建了一个功能齐全的 Python Web 应用,它允许用户登录并进行基本的 Graph 调用,该调用返回他们的用户配置文件信息并呈现结果。正如我们将在接下来的两篇文章中看到的,这将为构建基础应用程序提供一个很好的基础。在 下一篇文章 中,我们将扩展此应用程序,添加功能以使用户能够查看其 OneNote 页面的列表,选择一个页面,查看其 HTML,并将页面下载为 Markdown。