-
Notifications
You must be signed in to change notification settings - Fork 0
Security Model
何家欢 edited this page Jun 17, 2026
·
3 revisions
本页解释 Passkey-Auth 的安全模型、已做保护和仍需注意的边界。
可信后端:
- Passkey-Auth Flask 服务
- 业务系统后端
- 数据库
不可信输入:
- 浏览器 URL 参数
- 前端 JavaScript 传来的 credential JSON
- callback 参数中的
status=success - 用户输入的 username
核心原则:
- 浏览器负责完成 WebAuthn ceremony,但最终身份必须由后端判断。
- URL 里的成功状态不能直接登录。
- code 和 challenge 必须一次性消费。
- client secret 和 server token 永远只在后端。
注册:
- 使用
generate_registration_options - resident key required
- user verification preferred
- attestation none,减少隐私暴露
- exclude existing credentials,避免重复注册
登录:
- 使用
generate_authentication_options - 验证 challenge、RP ID、origin、public key、sign count
- 用户名登录时限制 allowCredentials
- 无用户名登录时通过 user handle 反查用户
相关代码:
passkey_demo/webauthn_service.py
authorization code:
- 随机生成
- 数据库存 hash
- 绑定
client_id - 绑定
redirect_uri - 有 TTL
- 只能消费一次
换 token 时会校验:
- client 是否存在
- client secret 是否匹配
- redirect_uri 是否匹配
- code 是否未过期、未消费
challenge request:
- 随机 challenge id
- 保存 username、state、return_uri、client_id
- 有 TTL
- completed 和 consumed 分离
- callback 校验后才 consumed
challenge result:
- 使用 Flask
SECRET_KEY通过itsdangerous.URLSafeTimedSerializer签名 - 绑定
challenge_id - 绑定
client_id - 绑定
state - 绑定用户
id和sub
callback 必须校验签名和数据库状态,不要信任 status=success。
OAuth 和 link challenge 都使用 state:
- 业务站点生成随机 state
- 保存到业务 session
- callback 先校验 state
- state 失败直接拒绝
不要复用 state,不要把 state 固定写死。
注册默认关闭:
PASSKEY_REGISTRATION_ENABLED=falseUI 层还有隐藏入口和短期解锁:
POST /api/ui/intentGET /api/ui/register-client.jsREGISTER_UNLOCK_TTL_SECONDS
这不是强安全认证,只是降低公开页面被随意注册的概率。真正生产系统应增加管理员审批、邀请码或后台用户管理。
FLASK_SECRET_KEY 影响:
- Flask session
- OAuth access token
- challenge result token
生产要求:
- 固定配置
- 强随机
- 不提交到 git
- 不随意更换
更换后影响:
- 旧 session 失效
- 旧 access token 失效
- 未完成的 challenge result 失效
当前项目是轻量认证服务,不是完整 IdP 产品。生产扩展建议:
- 多 client 管理
- client secret hash 存储
- refresh token
- token revocation
- audit log
- rate limiting
- admin console
- 数据库迁移工具
- 更严格的 CSP、安全 header
- 反向代理 HTTPS 强制跳转
- 业务 callback 已校验 state
- token 只在后端换取
-
client_secret不出现在浏览器 -
PASSKEY_SERVER_API_TOKEN不出现在浏览器 - callback URL 精确白名单
- code/challenge TTL 足够短
- 数据库不会被公开下载
-
.env、SQLite、.venv不提交 - 注册默认关闭
- HTTPS 正常