跳到主要内容

认证

您可以在您的 Flet 应用中使用第三方身份提供者(如GitHub、Google、Azure、Auth0、LinkedIn 等)实现用户身份验证(“登录使用 X”按钮)。

身份提供者必须支持 OAuth 2.0 授权码流程 来检索 API 访问令牌。

内置的 Flet 登录凭据和用户管理计划在未来发布中实现。 如果您的应用需要创建和管理用户帐户,您可以自行实现,或者使用 Auth0 身份提供者,它提供了一个慷慨的免费层级。

Flet 认证功能:

  • 适用于 Flet 桌面、Web 和移动应用。
  • 在一个应用中使用多个认证提供者。
  • 使用内置的 OAuth 提供者自动获取用户详细信息:
    • GitHub
    • Azure
    • Google
    • Auth0
  • 可选的组获取。
  • 自动令牌刷新。
  • 使用保存的令牌进行登录(“记住我”)。
  • 自定义 OAuth 提供者。

登录流程概述

  • 配置 OAuth 提供者(内置或通用),包括客户端 ID、客户端密钥、重定向 URL。
  • 调用 page.login(provider) 来启动 OAuth Web 流程。
  • 用户被重定向到 OAuth 提供者网站。
  • 在提供者网站上,用户登录并同意使用请求的范围访问服务 API。
  • 提供者网站将授权代码重定向到 Flet 的 OAuth 回调 URL。
  • Flet 交换授权代码以获取令牌,并调用 page.on_login 事件处理程序。
  • Flet 应用可以从 page.auth.token 属性检索 API 令牌和用户详细信息从 page.auth.user

配置 OAuth 提供者

Flet 具有以下内置的 OAuth 提供者:

  • GitHub
  • Azure
  • Google
  • Auth0

此外,您可以配置一个通用的 OAuth 提供者,并提供授权、令牌和用户信息端点。

在本指南中,我们将使用 GitHub 帐户配置 Flet 登录页面。

要将 Flet 认证与 GitHub 集成,首先应注册一个新的 GitHub OAuth 应用配置文件设置开发者设置OAuth 应用)。

授权回调 URL 应为以下格式:

{application-url}/oauth_callback

在 OAuth 应用详细信息页面上点击“生成新的客户端密钥”按钮。 将“客户端 ID”和“客户端密钥”值复制到安全位置 - 您将在 Flet 应用中使用它们。

使用 OAuth 提供者登录

import os

import flet as ft
from flet.auth.providers import GitHubOAuthProvider

GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
assert GITHUB_CLIENT_ID, "set GITHUB_CLIENT_ID environment variable"
GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
assert GITHUB_CLIENT_SECRET, "set GITHUB_CLIENT_SECRET environment variable"

def main(page: ft.Page):
provider = GitHubOAuthProvider(
client_id=GITHUB_CLIENT_ID,
client_secret=GITHUB_CLIENT_SECRET,
redirect_url="http://localhost:8550/oauth_callback",
)

def login_click(e):
page.login(provider)

def on_login(e):
print("登录错误:", e.error)
print("访问令牌:", page.auth.token.access_token)
print("用户 ID:", page.auth.user.id)

page.on_login = on_login
page.add(ft.ElevatedButton("使用 GitHub 登录", on_click=login_click))

ft.app(target=main, port=8550, view=ft.WEB_BROWSER)

:::注意 请注意,我们从环境变量中获取了 OAuth 应用客户端 ID 和客户端密钥。 不要将任何机密嵌入到源代码中,以避免意外暴露给公共! :::

在运行应用程序之前,请在命令行中设置密钥环境变量:

$ export GITHUB_CLIENT_ID="<client_id>"
$ export GITHUB_CLIENT_SECRET="<client_secret>"

运行程序并单击“使用 GitHub 登录”按钮。GitHub 授权应用程序页面将在:

  • 桌面 应用 - 新的浏览器窗口或标签页。
  • Web 应用程序 - 新的弹出窗口(确保弹出拦截器已关闭)。
  • 移动 应用程序 - 应用内网页浏览器。

重定向 URL

在注册 GitHub OAuth 应用时,我们使用了 http://localhost:8550/oauth_callback 作为重定向 URL。 请注意,它具有固定的端口 8550。要在固定端口上运行您的 Flet 应用程序,请在 flet.app 调用中使用 port 参数:

ft.app(target=main, port=8550)

范围

大多数 OAuth 提供程序允许应用程序请求一个或多个范围来限制应用程序对用户帐户的访问。

内置的 Flet 提供程序,默认情况下,请求访问用户配置文件的范围,但您可以在登录方法中请求额外的范围,比如在上面的示例中添加 public_repo:

page.login(
provider,
scope=["public_repo"])

page.login() 方法有许多参数来控制认证过程:

  • fetch_user (

bool) - 是否将用户详细信息获取到 page.auth.user。默认为 True

  • fetch_groups (bool) - 是否将用户组获取到 page.auth.user.groups。默认为 False
  • scope - 要请求的一组范围。
  • saved_token - page.auth.token 的 JSON 快照,以恢复授权。令牌可以使用 page.auth.token.to_json() 序列化,加密并保存在 page.client_storage 中。见下文。
  • on_open_authorization_url - 打开带有授权 URL 的浏览器的回调。见下文。
  • complete_page_html - “您已成功认证。现在请关闭此页面”的自定义 HTML 内容。
  • redirect_to_page (bool) - 仅用于 Flet Web 应用程序,当在同一浏览器选项卡中打开授权页面时使用。

page.login() 调用的结果是一个 Authorization 类的实例,具有以下字段:

  • token - 用于访问提供者的 API 的 OAuth 令牌。见下文。
  • user - 包含用户详细信息的用户配置文件,其中包含必填的 id 字段和其他特定于 OAuth 提供程序的字段。
  • provider - 用于授权的 OAuth 提供程序的实例。

对于具有多个 OAuth 提供程序的授权的应用程序,您可以将授权保存在会话中, 例如:

page.session["github_auth"] = page.login(github_provider)
page.session["google_auth"] = page.login(google_provider)

检查认证结果

在成功或失败的认证后,将调用 page.on_login 事件处理程序。

事件处理程序参数 eLoginEvent 类的实例,具有以下属性:

  • error (str) - OAuth 错误。
  • error_description (str) - OAuth 错误描述。

如果 error 是空字符串,则授权成功。

您可以使用此事件处理程序来切换登录/退出 UI,例如:

import os

import flet
from flet import ElevatedButton, LoginEvent, Page
from flet.auth.providers import GitHubOAuthProvider

def main(page: Page):
provider = GitHubOAuthProvider(
client_id=os.getenv("GITHUB_CLIENT_ID"),
client_secret=os.getenv("GITHUB_CLIENT_SECRET"),
redirect_url="http://localhost:8550/oauth_callback",
)

def login_button_click(e):
page.login(provider, scope=["public_repo"])

def on_login(e: LoginEvent):
if not e.error:
toggle_login_buttons()

def logout_button_click(e):
page.logout()

def on_logout(e):
toggle_login_buttons()

def toggle_login_buttons():
login_button.visible = page.auth is None
logout_button.visible = page.auth is not None
page.update()

login_button = ElevatedButton("使用 GitHub 登录", on_click=login_button_click)
logout_button = ElevatedButton("登出", on_click=logout_button_click)
toggle_login_buttons()
page.on_login = on_login
page.on_logout = on_logout
page.add(login_button, logout_button)

flet.app(target=main, port=8550, view=flet.WEB_BROWSER)

访问用户详细信息

如果调用 page.login() 方法时使用 fetch_user=True(默认值),则用户配置文件将分配给 page.auth.user

所有内置的 OAuth 提供程序都实现了 user.id 属性 - 唯一用户标识符 - 其值取决于提供程序(数字、Guid 或电子邮件)并且可以在您的应用程序中用作用户键。

用户配置文件的其余属性取决于提供者,并且可以使用索引器访问。 例如,要打印 GitHub 用户的一些属性:

print("姓名:", page.auth.user["name"])
print("登录:", page.auth.user["login"])
print("电子邮件:", page.auth.user["email"])

使用 OAuth 令牌

在成功授权后,page.auth.token 将包含 OAuth 令牌,该令牌可用于访问提供者的 API。令牌对象具有以下属性:

  • access_token - 作为 API 请求头中的授权令牌使用的访问令牌。
  • scope - 令牌的范围。
  • token_type - 访问令牌类型,例如 Bearer
  • expires_in - 可选的访问令牌过期的秒数。
  • expires_at - 可选的访问令牌过期的时间(time.time() + expires_in)。
  • refresh_token - 可选的刷新令牌,在旧令牌过期时用于获取新的访问令牌。

通常,只需要 page.auth.token.access_token 来调用提供者的 API, 例如列出用户的 GitHub 仓库:

import requests
headers = {"Authorization": "Bearer {}".format(page.auth.token.access_token)}
repos_resp = requests.get("https://api.github.com/user/repos", headers=headers)
user_repos = json.loads(repos_resp.text)
for repo in user_repos:
print(repo["full_name"])

:::注意 不要在代码中的某个地方保存对 page.auth.token 的引用,而是每次需要获取访问令牌时都调用 page.auth.tokenpage.auth.token 是一个属性,当令牌过期时会自动刷新。 :::

正确的代码:

access_token = page.auth.token.access_token

错误的代码:

token = page.auth.token
# some other code
access_token = token.access_token # token could expire by this moment

:::

保存和恢复身份验证令牌

要实现持久登录(在登录页面上的“记住我”复选框),您可以将身份验证令牌保存在客户端存储中,并在用户下次打开您的 Flet 应用程序时使用它进行登录。

将身份验证令牌序列化为 JSON:

jt = page.auth.token.to_json()
警告

在将数据发送到客户端存储之前加密敏感数据。

Flet 包含用于使用对称算法(即使用相同密钥进行加密和解密)加密文本数据的实用方法。它们使用 cryptography 包中的 Fernet 实现,该实现是 AES 128 加上一些额外的加固,再加上 PBKDF2 从用户密码派生加密密钥。

要加密 JSON 令牌:

import os
from flet.security import encrypt, decrypt

secret_key = os.getenv("MY_APP_SECRET_KEY")
# 返回 base64 编码的字符串
ejt = encrypt(jt, secret_key)
警告

请注意,我们正在从环境变量中获取密钥(也称为密码、口令等)。不要将任何密钥嵌入到源代码中,以避免意外暴露给公众!

在运行应用程序之前,通过命令行设置密钥:

$ export MY_APP_SECRET_KEY="<secret>"

现在,加密后的值可以存储在客户端存储中:

page.client_storage.set("myapp.auth_token", ejt)

下次用户打开应用程序时,您可以从客户端存储中读取加密的令牌,并且如果存在,解密它并在 page.login() 方法中使用:

ejt = page.client_storage.get("myapp.auth_token")
if ejt:
jt = decrypt(ejt, secret_key)
page.login(provider, saved_token=jt)

查看完整应用示例

注销

调用 page.logout() 将重置 page.auth 引用并触发 page.on_logout 事件处理程序。

您可以在注销方法中删除保存的令牌,例如:

def logout_button_click(e):
page.client_storage.remove(AUTH_TOKEN_KEY)
page.logout()

查看完整应用示例

自定义授权流程

默认情况下,OAuth 授权流程发生在新的浏览器窗口/选项卡(桌面应用程序)、浏览器弹出窗口(Web)或应用内 Web 视图(移动设备)中。

完成授权流程后,用户将被重定向到 Flet 的 OAuth 回调页面(/oauth_callback),该页面尝试使用 JavaScript 关闭浏览器窗口/选项卡,并向用户提供关闭窗口的手动说明,如果 JavaScript 关闭未成功。

此部分仅适用于 Flet 桌面和 Web 应用程序,因为移动应用程序中的应用内 Web 视图可以在不依赖 JavaScript 的情况下由 Flet 关闭。

您可以在 page.login() 方法中自定义“授权完成”页面的内容,例如:

complete_page_html = """
<!DOCTYPE html>
<html>
<head>
<title>已登录到 MyApp</title>
</head>
<body>
<script type="text/javascript">
window.close();
</script>
<p>您已成功登录!您现在可以关闭此选项卡或窗口。</p>
</body>
</html>
"""

page.login(
provider,
complete_page_html=complete_page_html,
)

您还可以将 Web 应用程序更改为在同一选项卡中打开提供商的授权页面,这可能更符合用户的习惯,并且可以避免弹出窗口拦截器:

page.login(
provider,
on_open_authorization_url=lambda url: page.launch_url(url, web_window_name="_self"),
redirect_to_page=True
)

要在新选项卡中打开流程(注意将 _self 替换为 _blank):

page.login(
provider,
on_open_authorization_url=lambda url: page.launch_url(url, web_window_name="_blank")
)

配置自定义 OAuth 提供商

您可以使用 flet.auth.oauth_provider.OAuthProvider 类在您的应用程序中配置任何符合 OAuth 的身份验证提供程序。

通过按照LinkedIn 授权码流程指南中的说明,我们能够获取配置 LinkedIn OAuth 提供程序所需的所有必要参数,以允许您的 Flet 应用程序的用户使用他们的 LinkedIn 帐户登录:

import os

import flet
from flet import ElevatedButton, Page
from flet.auth import OAuthProvider

def main(page: Page):
provider = OAuthProvider(
client_id=os.getenv("LINKEDIN_CLIENT_ID"),
client_secret=os.getenv("LINKEDIN_CLIENT_SECRET"),
authorization_endpoint="https://www.linkedin.com/oauth/v2/authorization",
token_endpoint="https://www.linkedin.com/oauth/v2/accessToken",
user_endpoint="https://api.linkedin.com/v2/me",
user_scopes=["r_liteprofile", "r_emailaddress"],
user_id_fn=lambda u: u["id"],
redirect_url="http://localhost:8550/oauth_callback",
)

def login_click(e):
page.login(provider)

def on_login(e):
if e.error:
raise Exception(e.error)
print("User ID:", page.auth.user.id)
print("Access token:", page.auth.token.access_token)

page.on_login = on_login
page.add(ElevatedButton("使用 LinkedIn 登录", on_click=login_click))



flet.app(target=main, port=8550, view=flet.WEB_BROWSER)

必填提供商设置:

  • client_id
  • client_secret
  • authorization_endpoint
  • token_endpoint
  • redirect_url

与其他示例类似,客户端 ID 和客户端密钥从环境变量中获取。