flask-pluginkit-oidc
基于 Authlib 的 OIDC Client,作为 Flask-PluginKit 插件使用,对接 staugur/passportd OIDC Provider。
依赖
- Python >= 3.9
- flask-pluginkit >= 3.11.0
- authlib >= 1.7.0
快速开始
1. 安装
pip install flask-pluginkit-oidc
# 或从 Git 安装
pip install git+https://github.com/saintic/flask-pluginkit-oidc@master
2. 配置
通过环境变量或 app.config 设置以下配置:
| 配置项 | 环境变量 | 必填 | 说明 |
|---|---|---|---|
PASSPORTD_OIDC_CLIENT_ID |
PASSPORTD_OIDC_CLIENT_ID |
是 | OIDC Provider 分配的 client_id |
PASSPORTD_OIDC_CLIENT_SECRET |
PASSPORTD_OIDC_CLIENT_SECRET |
是 | OIDC Provider 分配的 client_secret |
PASSPORTD_OIDC_SERVER_METADATA_URL |
PASSPORTD_OIDC_SERVER_METADATA_URL |
否 | OIDC Discovery 端点,默认 https://passport.saintic.com/.well-known/openid-configuration |
PASSPORTD_OIDC_CLIENT_KWARGS |
— | 否 | 传递给 OAuth client 的额外参数,默认 {"scope": "openid profile"} |
PASSPORTD_OIDC_STATE |
PASSPORTD_OIDC_STATE |
否 | 插件启用状态,默认是enabled,禁用是disabled |
PASSPORTD_OIDC_STATE_STORE |
PASSPORTD_OIDC_STATE_STORE |
否 | OAuth state 存储方式:session(默认,存客户端 session cookie)或 redis(存服务端 Redis) |
PASSPORTD_OIDC_REDIS_URL |
PASSPORTD_OIDC_REDIS_URL |
否 | Redis 连接串,仅 STATE_STORE=redis 时使用,默认 redis://localhost:6379/0 |
PASSPORTD_OIDC_REDIS |
— | 否 | 直接传入 Redis 客户端实例(优先级高于 REDIS_URL),仅 STATE_STORE=redis 时使用 |
PASSPORTD_OIDC_STATE_EXPIRES |
PASSPORTD_OIDC_STATE_EXPIRES |
否 | state 过期时间(秒),仅 STATE_STORE=redis 时使用,默认 3600 |
Authlib 约定:
oauth.register(name="passportd_oidc")会自动从app.config查找PASSPORTD_OIDC_CLIENT_ID和PASSPORTD_OIDC_CLIENT_SECRET,无需手动传入。
3. 使用
from os import getenv
from flask import Flask, session, g, make_response, redirect
from flask_pluginkit import PluginManager
app = Flask(__name__)
app.secret_key = getenv("SECRET_KEY", "change-me")
app.config.update(
PASSPORTD_OIDC_CLIENT_ID=getenv("PASSPORTD_OIDC_CLIENT_ID", ""),
PASSPORTD_OIDC_CLIENT_SECRET=getenv("PASSPORTD_OIDC_CLIENT_SECRET", ""),
)
plugin = PluginManager(app, plugin_packages=["flask_pluginkit_oidc"])
def set_login_state(userinfo:dict):
# 假设用session管理会话
session["user"] = userinfo
return make_response(redirect("/"))
@app.before_request
def before_request():
# 强烈建议, 设置登录状态,如果返回 Flask.Response 对象, 扩展会直接 return 对象。
g.set_login_state = set_login_state
# 可选,登录后跳转地址
g.login_redirect_url = "/"
if __name__ == "__main__":
app.run(debug=True)
4. 测试
export PASSPORTD_OIDC_CLIENT_ID=your_client_id
export PASSPORTD_OIDC_CLIENT_SECRET=your_client_secret
python test_client.py
访问 http://localhost:5000/oauth2/passportd/login 发起 OIDC 登录。
5. userinfo
{
"bio": "签名",
"gender": 1,
"location": "地点",
"nickname": "昵称",
"picture": "头像地址",
"status": 1,
"sub": "用户唯一标识UID"
}
6. 解决 OIDC state 丢失问题(可选)
Authlib 默认把 OAuth state(含 nonce、code_verifier)写入 Flask 客户端 session cookie。
部分浏览器(如 Brave)会阻止跨站 Cookie:用户从 Provider 授权页回调 /authorized 时,
session cookie 丢失,Authlib 会抛出 MismatchingStateError(500)。
插件已内置兜底:即使 state 丢失也不会 500,而是重定向回登录页重新发起授权。
若要彻底规避该问题,可将 state 改为存到服务端 Redis:
app.config.update(
PASSPORTD_OIDC_STATE_STORE="redis",
PASSPORTD_OIDC_REDIS_URL=getenv("PASSPORTD_OIDC_REDIS_URL", "redis://localhost:6379/0"),
)
# 或者直接传入 Redis 客户端实例(需先 pip install redis)
# PASSPORTD_OIDC_REDIS=redis_client,
路由
| 路由 | 说明 |
|---|---|
/oauth2/passportd/login |
发起 OIDC 授权,重定向至 Provider 登录页 |
/oauth2/passportd/authorized |
OIDC 回调地址,Provider 需配置为此 URL |
/oauth2/passportd/profile |
跳转到 OIDC Provider 的用户资料页 |
暴露 OIDC Server 信息
插件在 on_app_ready 时将共享信息挂到 app.extensions["flask_pluginkit_oidc"],外部模块 / 模板可直接复用:
# 外部模块中
oidc_meta = current_app.extensions["flask_pluginkit_oidc"]
oidc_meta["server_url"] # OIDC Provider 的 issuer(discovery 文档中的签发地址),如 https://passport.saintic.com
oidc_meta["client"] # Authlib OAuth 客户端实例
server_url即 OIDC discovery 文档中的issuer字段(签发地址),仅当其是合法的 http/https URL 时才返回;插件在on_app_ready时加载一次,Authlib 会缓存 discovery 文档。
模板中也可通过 app.jinja_env.globals 自行挂载后使用。
跳转用户资料页
/oauth2/passportd/profile 视图直接重定向到 OIDC Provider 的 issuer(签发地址);若 issuer 非法或为空,则兜底跳转首页 /。
工作原理
- 插件通过 Flask-PluginKit 的
register()入口加载,注册 Blueprint on_app_ready(app)在应用完全就绪后调用(Flask-PluginKit >= 3.11.0),此时有应用上下文,安全地初始化oauth.init_app(app)并注册 OIDC 客户端;若配置PASSPORTD_OIDC_STATE_STORE=redis,同时将 OAuth state 存储切换到 Redis- 用户访问
/login→ 重定向到 OIDC Provider 授权页 - Provider 认证后回调
/authorized→ Authlib 完成 token 交换 + userinfo 获取;若 state 校验失败(如跨站 Cookie 被阻止),自动重定向回登录页而非 500 - userinfo 默认写入
session["user"] = userinfo或通过g.set_login_state(userinfo)设置登录状态 - 重定向到
g.login_redirect_url或/
Metadata
Release files for flask-pluginkit-oidc 0.4.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flask_pluginkit_oidc-0.4.2.tar.gz | 8.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flask_pluginkit_oidc-0.4.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 16.9 kB
Release files / flask_pluginkit_oidc-0.4.2.tar.gz
| Download URL | flask_pluginkit_oidc-0.4.2.tar.gz |
|---|---|
| Size | 8.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1d2f66eeeb16162a2d06fbab726d9c8e15cbe063793db937d2ffc99de3e554a9
|
|
BLAKE2b-256 checksum How to use checksums |
f31b19645d4ab1599d3299faa6d644f7d9e17b36658882dda97d88e28dff17f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.9
|
Release files / flask_pluginkit_oidc-0.4.2-py3-none-any.whl
| Download URL | flask_pluginkit_oidc-0.4.2-py3-none-any.whl |
|---|---|
| Size | 8.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c6c1c43b0bdbdbfb9bf25deb9e2657bba417e09af306e2d1e3a3f74a59c0aafa
|
|
BLAKE2b-256 checksum How to use checksums |
9fcf422550925cf4f5d560553d4a4ee00a9e4a3f068234749482d95a58afb602
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.9
|