deer-flow/backend/docs/AUTH_DESIGN.md
yym36991 e7e9c51988
Classify four HTTP identity sources (browser session, OIDC, IM channel (#4179)
binding, Internal Auth) and group IM bindings with direct HTTP under a
shared platform-trust model. Explain Internal Auth trust boundaries:
DeerFlow validates only the platform token, does not manage end-user
accounts in users, and relies on the channel to maintain owner identity.
Cross-link AUTH_DESIGN, API.md, and docs README
2026-07-15 19:42:35 +08:00

22 KiB
Raw Permalink Blame History

用户认证与隔离设计

本文档描述 DeerFlow 当前内置认证模块的设计,而不是历史 RFC。它覆盖浏览器登录、OIDC/SSO、平台信任接入IM Channel 与 Internal Auth、API 认证、CSRF、用户隔离、首次初始化、密码重置和升级迁移。

设计目标

认证模块的核心目标是把 DeerFlow 从“本地单用户工具”提升为“可多用户部署的 agent runtime”并让用户身份贯穿 HTTP API、LangGraph-compatible runtime、文件系统、memory、自定义 agent 和反馈数据。

设计约束:

  • 默认强制认证:除健康检查、文档和 auth bootstrap 端点外HTTP 路由都必须有有效 session。
  • 服务端持有所有权:客户端 metadata 不能声明 user_idowner_id
  • 隔离默认开启repository仓储、文件路径、memory、agent 配置默认按当前用户解析。
  • 旧数据可升级:无认证版本留下的 thread 可以在 admin 存在后迁移到 admin。
  • 密码不进日志:首次初始化由操作者设置密码;reset_admin 只写 0600 凭据文件。

非目标:

  • 当前用户角色只有 adminuser,尚未实现细粒度 RBAC。
  • 当前登录限速是进程内字典,多 worker 下不是全局精确限速。

核心模型

graph TB
  classDef actor fill:#D8CFC4,stroke:#6E6259,color:#2F2A26;
  classDef api fill:#C9D7D2,stroke:#5D706A,color:#21302C;
  classDef state fill:#D7D3E8,stroke:#6B6680,color:#29263A;
  classDef data fill:#E5D2C4,stroke:#806A5B,color:#30251E;

  Browser["Browser — access_token cookie and csrf_token cookie"]:::actor
  AuthMiddleware["AuthMiddleware — strict session gate"]:::api
  CSRFMiddleware["CSRFMiddleware — double-submit token and Origin check"]:::api
  AuthRoutes["Auth routes — initialize login register logout me change-password"]:::api
  UserContext["Current user ContextVar — request-scoped identity"]:::state
  Repositories["Repositories — AUTO resolves user_id from context"]:::state
  Files["Filesystem — users/{user_id}/threads/{thread_id}/user-data"]:::data
  Memory["Memory and agents — users/{user_id}/memory.json and agents"]:::data

  Browser --> AuthMiddleware
  Browser --> CSRFMiddleware
  AuthMiddleware --> AuthRoutes
  AuthMiddleware --> UserContext
  UserContext --> Repositories
  UserContext --> Files
  UserContext --> Memory

用户表

用户记录定义在 app.gateway.auth.models.User,持久化到 users 表。关键字段:

字段 语义
id 用户主键JWT sub 使用该值
email 唯一登录名
password_hash bcrypt hashOAuth 用户可为空
system_role adminuser
needs_setup reset 后要求用户完成邮箱 / 密码设置
token_version 改密码或 reset 时递增,用于废弃旧 JWT

运行时身份

认证成功后,AuthMiddleware 把用户同时写入:

  • request.state.user
  • request.state.auth
  • deerflow.runtime.user_contextContextVar

ContextVar 是这里的核心边界。上层 Gateway 负责写入身份,下层 persistence / file path 只读取结构化的当前用户,不反向依赖 app.gateway.auth 具体类型。

可以把 repository 调用的用户参数理解成一个三态 ADT

enum UserScope:
  case AutoFromContext
  case Explicit(userId: String)
  case BypassForMigration

对应 Python 实现是 AUTO | str | None

  • AUTO:从 ContextVar 解析当前用户;没有上下文则抛错。
  • str:显式指定用户,主要用于测试或管理脚本。
  • None:跳过用户过滤,只允许迁移脚本或 admin CLI 使用。

登录与初始化流程

首次初始化

首次启动时,如果没有 admin服务不会自动创建账号只记录日志提示访问 /setup

流程:

  1. 用户访问 /setup
  2. 前端调用 GET /api/v1/auth/setup-status
  3. 如果返回 {"needs_setup": true},前端展示创建 admin 表单。
  4. 表单提交 POST /api/v1/auth/initialize
  5. 服务端确认当前没有 admin创建 system_role="admin"needs_setup=false 的用户。
  6. 服务端设置 access_token HttpOnly cookie用户进入 workspace。

/api/v1/auth/initialize 只在没有 admin 时可用。并发初始化由数据库唯一约束兜底,失败方返回 409。

普通登录

POST /api/v1/auth/login/local 使用 OAuth2PasswordRequestForm

  • username 是邮箱。
  • password 是密码。
  • 成功后签发 JWT放入 access_token HttpOnly cookie。
  • 响应体只返回 expires_inneeds_setup,不返回 token。

登录失败会按客户端 IP 计数。IP 解析只在 TCP peer 属于 AUTH_TRUSTED_PROXIES 时信任 X-Real-IP,不使用 X-Forwarded-For

注册

POST /api/v1/auth/register 创建普通 user,并自动登录。

当前实现允许在没有 admin 时注册普通用户,但 setup-status 仍会返回 needs_setup=true,因为 admin 仍不存在。这是当前产品策略边界:如果后续要求“必须先初始化 admin 才能注册普通用户”,需要在 /register 增加 admin-exists gate。

改密码与 reset setup

POST /api/v1/auth/change-password 需要当前密码和新密码:

  • 校验当前密码。
  • 更新 bcrypt hash。
  • token_version += 1,使旧 JWT 立即失效。
  • 重新签发 cookie。
  • 如果 needs_setup=true 且传了 new_email,则更新邮箱并清除 needs_setup

python -m app.gateway.auth.reset_admin 会:

  • 找到 admin 或指定邮箱用户。
  • 生成随机密码。
  • 更新密码 hash。
  • token_version += 1
  • 设置 needs_setup=true
  • 写入 .deer-flow/admin_initial_credentials.txt,权限 0600

命令行只输出凭据文件路径,不输出明文密码。

HTTP 认证边界

AuthMiddleware 是 fail-closed默认拒绝的全局认证门。

公开路径:

  • /health
  • /docs
  • /redoc
  • /openapi.json
  • /api/v1/auth/login/local
  • /api/v1/auth/register
  • /api/v1/auth/logout
  • /api/v1/auth/setup-status
  • /api/v1/auth/initialize
  • /api/v1/auth/providers
  • /api/v1/auth/oauth/ (所有子路径)
  • /api/v1/auth/callback/ (所有子路径)

其余路径都要求有效 access_token cookie。存在 cookie 但 JWT 无效、过期、用户不存在或 token_version 不匹配时,直接返回 401而不是让请求穿透到业务路由。

路由级别的 owner check 由 require_permission(..., owner_check=True) 完成:

  • 读类请求允许旧的未追踪 legacy thread 兼容读取。
  • 写 / 删除类请求使用 require_existing=True,要求 thread row 存在且属于当前用户,避免删除后缺 row 导致其他用户误通过。

CSRF 设计

DeerFlow 使用 Double Submit Cookie

  • 服务端设置 csrf_token cookie。
  • 前端 state-changing 请求发送同值 X-CSRF-Token header。
  • 服务端用 secrets.compare_digest 比较 cookie/header。

需要 CSRF 的方法:

  • POST
  • PUT
  • DELETE
  • PATCH

auth bootstrap 端点login/register/initialize/logout不要求 double-submit token因为首次调用时浏览器还没有 token但这些端点会校验 browser Origin,拒绝 hostile Origin避免 login CSRF / session fixation。

用户隔离

Thread metadata

Thread metadata 存在 threads_meta,关键隔离字段是 user_id

创建 thread 时:

  • 客户端传入的 metadata.user_idmetadata.owner_id 会被剥离。
  • ThreadMetaRepository.create(..., user_id=AUTO)ContextVar 解析真实用户。
  • /api/threads/search 默认只返回当前用户的 thread。

读取 / 修改 / 删除时:

  • get() 默认按当前用户过滤。
  • check_access() 用于路由 owner check。
  • 对其他用户的 thread 返回 404避免泄露资源存在性。

文件系统

当前线程文件布局:

{base_dir}/users/{user_id}/threads/{thread_id}/user-data/
├── workspace/
├── uploads/
└── outputs/

agent 在 sandbox 内看到统一虚拟路径:

/mnt/user-data/workspace
/mnt/user-data/uploads
/mnt/user-data/outputs

ThreadDataMiddleware 使用 get_effective_user_id() 解析当前用户并生成线程路径。没有认证上下文时会落到 default 用户桶,主要用于内部调用、嵌入式 client 或无 HTTP 的本地执行路径。

Memory

默认 memory 存储:

{base_dir}/users/{user_id}/memory.json
{base_dir}/users/{user_id}/agents/{agent_name}/memory.json

有用户上下文时,空或相对 memory.storage_path 都使用上述 per-user 默认路径;只有绝对 memory.storage_path 会视为显式 opt-out退出 per-user isolation所有用户共享该路径。无用户上下文的 legacy 路径仍会把相对 storage_path 解析到 Paths.base_dir 下。

自定义 agent

用户自定义 agent 写入:

{base_dir}/users/{user_id}/agents/{agent_name}/
├── config.yaml
├── SOUL.md
└── memory.json

旧布局 {base_dir}/agents/{agent_name}/ 只作为只读兼容回退。更新或删除旧共享 agent 会要求先运行迁移脚本。

认证方式总览

DeerFlow 支持四类彼此独立的 HTTP 身份来源。它们共享同一套 thread / run 隔离语义threads_meta.user_idruns.user_id.deer-flow/users/{user_id}/threads/...),但在 是否写入 users外部身份如何映射 上不同。

方式 典型入口 写入 users 外部身份映射 用户 / thread 隔离
浏览器本地账号 POST /api/v1/auth/login/local/registeraccess_token cookie 邮箱即 DeerFlow users.id threads_meta.user_id = users.id
OIDC / SSO GET /api/v1/auth/oauth/{provider} → callback → cookie 是(自动创建或关联) IdP subusers.oauth_id 同上
IM Channel 绑定 Settings 里 Connect + 平台侧 /connect <code> 绑定到已注册 DeerFlow 用户 channel_connections / channel_conversations owner_user_idusers.id
Internal Auth直接 HTTP X-DeerFlow-Internal-Token + X-DeerFlow-Owner-User-Id (合成 internal 用户) 平台在 header 中自声明 owner 字符串 threads_meta.user_id = owner(经 make_safe_user_id 规范化)
graph TB
  classDef browser fill:#C9D7D2,stroke:#5D706A,color:#21302C
  classDef platform fill:#D7D3E8,stroke:#6B6680,color:#29263A
  classDef store fill:#E5D2C4,stroke:#806A5B,color:#30251E

  Browser["浏览器会话<br/>cookie JWT"]:::browser
  OIDC["OIDC / SSO<br/>OAuth callback"]:::browser
  IM["IM Channel 绑定<br/>connect code"]:::platform
  Internal["Internal Auth<br/>共享平台密钥 + Owner header"]:::platform
  Users[("users 表")]:::store
  Threads[("threads_meta / runs / checkpoints")]:::store
  Bindings[("channel_connections<br/>channel_conversations")]:::store

  Browser --> Users
  OIDC --> Users
  IM --> Bindings --> Users
  IM --> Threads
  Internal --> Threads

OIDC 细节见 SSO.md。IM 绑定细节见 IM_CHANNEL_CONNECTIONS.md

平台信任接入

IM Channel 绑定Internal Auth 可归为同一大类:平台信任模型——DeerFlow 把渠道/合作平台视为已认证边界,由平台把“自己的用户”映射到 DeerFlow 的运行时身份,而不是让每个终端用户再走 DeerFlow 注册登录。

维度 IM Channel 绑定(子类 A Internal Auth 直接 HTTP子类 B
平台凭证 channels.* 机器人配置 + Gateway 内部调用 部署级 DEER_FLOW_INTERNAL_AUTH_TOKEN
DeerFlow 用户来源 必须绑定到 users 表中的真实账号 不创建 users 行;使用合成 system_role=internal 用户
外部身份登记 channel_connections + channel_conversations(可审计、可撤销) 请求头 X-DeerFlow-Owner-User-Id(平台自声明,如 feishu_ou_alice
典型调用方 DeerFlow 内置 IM worker飞书 / 企业微信 / Slack / Telegram …) 合作方后端(如飞书或企业微信自建应用网关)
身份可信度 Connect code 一次性绑定DB 唯一约束保证单 owner 完全信任平台对 Owner-User-Id 的正确性
用户 / thread 隔离 有(按绑定的 owner_user_id 有(按 header 中的 owner 字符串)
本地文件布局 .deer-flow/users/{owner}/threads/{thread_id}/... 同上

两类接入在 run 生命周期上共用同一持久化面:threads_metarunsrun_eventscheckpointscheckpoint_blobs 都按解析后的 user_id 做隔离;差异只在 owner 是否来自 users.id 还是平台声明的字符串。

Internal Auth (direct HTTP)

适用于“平台后端代替终端用户调用 DeerFlow API”的集成平台持有共享密钥替每个业务用户附带 owner 标识。类似飞书或企业微信机器人网关把已认证用户代理到 DeerFlow不经过 IM connect-code 绑定表。

配置

Gateway 启动时设置环境变量:

export DEER_FLOW_INTERNAL_AUTH_TOKEN="<long-random-secret>"

未配置时 Gateway 会为每个 worker 进程生成随机 token不利于多副本或与集成方对齐生产环境应显式配置并仅在内网可达的调用链中分发。

请求头

Header 必填 说明
X-DeerFlow-Internal-Token 必须等于 Gateway 的 DEER_FLOW_INTERNAL_AUTH_TOKEN;缺失或错误 → 401
X-DeerFlow-Owner-User-Id 需要用户隔离时必填 平台侧用户标识,如 feishu_ou_alice(飞书 open_id)或 wecom_user_bob(企业微信成员 id同一用户的建 thread / 续聊应保持一致。缺失时落到 default 用户桶

Internal Auth 不使用浏览器 access_token cookie不参与前端 CSRF double-submit cookie 流程。DeerFlow 内置 IM worker 在进程内同时附带 Internal Token 与 CSRF cookie/header第三方平台做 server-to-server HTTP 集成时通常只发送 Internal 相关 header。

合成用户由 get_internal_user() 构造:system_role="internal"idmake_safe_user_id(owner)default不会users 表插入记录。

数据落库与隔离

存储 Internal Auth 行为
users 不写入
threads_meta.user_id X-DeerFlow-Owner-User-Id(规范化后)
runs.user_id 同上
run_events / checkpoints / checkpoint_blobs 随 thread / run 归属,与浏览器用户相同隔离规则
本地目录 .deer-flow/users/{owner}/threads/{thread_id}/user-data/...

threads/search、thread owner check、文件路径解析均按上述 user_id 过滤;不同 owner 之间 thread 互不可见。

信任边界与 DeerFlow 职责

Internal Auth 是平台信任模型,不是终端用户认证:

  • DeerFlow 只校验调用方是否持有有效的 X-DeerFlow-Internal-Token(平台级共享密钥)。
  • DeerFlow 不校验 X-DeerFlow-Owner-User-Id 是否对应真实、活跃、已授权的业务用户;该字段仅作为运行时隔离键使用。
  • DeerFlow 不管理这类用户的注册、登录、登出、密码、禁用或吊销;终端用户的有效性、会话与权限全部由渠道/平台自行维护
  • DeerFlow 不写入 users 表,不为 Internal Auth 用户签发 JWT也不提供面向终端的账号生命周期 API。

因此DeerFlow 与渠道之间的契约是:信任渠道已经替终端用户完成认证,并诚实地在每次请求中标注 owner。若共享 token 泄露给终端或平台未校验用户身份就转发请求DeerFlow 无法阻止 Owner-User-Id 被伪造。

对接方式

接入方使用与普通 Gateway API 相同的 thread / run 端点,在每次请求中附带 Internal 相关 header 即可,例如:

  1. POST /api/threads — 创建会话(thread_idmetadata 等 body 字段语义不变)
  2. POST /api/threads/{thread_id}/runs/stream — 流式对话;续聊时保持同一 thread_id 与同一 X-DeerFlow-Owner-User-Id

具体路径、请求体与 stream_mode 等参数见 API.mdSTREAMING.md。本文档不展开 curl 测试用例。

安全建议

  • 推荐:平台后端持有 DEER_FLOW_INTERNAL_AUTH_TOKEN,按已认证业务用户设置 X-DeerFlow-Owner-User-Id
  • 不推荐:把共享 token 直接发给每个终端用户自行调用——此时终端用户只需伪造 Owner-User-Id 即可冒充他人DeerFlow 无法验证平台侧身份。
  • Token 应视为部署密钥:不进 git、不写入前端、仅通过内网或 mTLS 保护的后端链路传输。

IM Channel 绑定(子类 A

IM worker 通过 Gateway 内部 HTTP 调用 agent runtime并携带

  • X-DeerFlow-Internal-Token
  • 匹配的 CSRF cookie / X-CSRF-Token(进程内生成,供 worker 使用)
  • 绑定成功后附带 X-DeerFlow-Owner-User-Id(来自 channel_connections.owner_user_id,对应 users.id

与 Internal Auth 直接 HTTP 相比IM 路径多了 connect-code 绑定users 表关联,外部身份可追溯、可撤销。配置与运维见 IM_CHANNEL_CONNECTIONS.md

LangGraph-compatible 认证

Gateway 内嵌 runtime 路径由 AuthMiddlewareCSRFMiddleware 保护。

仓库仍保留 app.gateway.langgraph_auth,用于 LangGraph Server 直连模式:

  • @auth.authenticate 校验 JWT cookie、CSRF、用户存在性和 token_version
  • @auth.on 在写入 metadata 时注入 user_id,并在读路径返回 {"user_id": current_user} 过滤条件。

这保证 Gateway 路由和 LangGraph-compatible 直连模式使用同一 JWT 语义。

升级与迁移

从无认证版本升级时,可能存在没有 user_id 的历史 thread。

当前策略:

  1. 首次启动如果没有 admin只提示访问 /setup,不迁移。
  2. 操作者创建 admin。
  3. 后续启动时,_ensure_admin_user() 找到 admin并把 LangGraph store 中缺少 metadata.user_id 的 thread 迁移到 admin。

文件系统旧布局迁移由脚本处理:

cd backend
PYTHONPATH=. python scripts/migrate_user_isolation.py --dry-run
PYTHONPATH=. python scripts/migrate_user_isolation.py --user-id <target-user-id>

迁移脚本覆盖 legacy memory.jsonthreads/agents/ 到 per-user layout。

安全不变量

必须长期保持的不变量:

  • JWT 只在 HttpOnly cookie 中传输,不出现在响应 JSON。
  • 任何非 public HTTP 路由都不能只靠“cookie 存在”放行,必须严格验证 JWT。
  • token_version 不匹配必须拒绝,保证改密码 / reset 后旧 session 失效。
  • 客户端 metadata 中的 user_id / owner_id 必须剥离。
  • repository 默认 AUTO 必须从当前用户上下文解析,不能静默退化成全局查询。
  • 只有迁移脚本和 admin CLI 可以显式传 user_id=None 绕过隔离。
  • 本地文件路径必须通过 Paths 和 sandbox path validation 解析,不能拼接未校验的用户输入。
  • 捕获认证、迁移、后台任务异常必须记录日志;不能空 catch。

已知边界

边界 当前行为 后续方向
无 admin 时注册普通用户 允许注册普通 user 如产品要求先初始化 admin/register 加 gate
登录限速 进程内 dict单 worker 精确,多 worker 近似 Redis / DB-backed rate limiter
OAuth / OIDC 已实现通用 OIDC SSOKeycloak, Google, Azure AD, Okta 等),支持 PKCE + nonce、auto-provisioning、email domain 限制(详见 SSO.md 支持 RP-initiated logout、自定义 scope 映射
IM 用户隔离 channel_connections 绑定到 users.id;未绑定消息在 require_bound_identity: true 时被拒绝 更多渠道与审计能力
Internal Auth 终端直持 token 平台可把共享密钥下发给终端,导致 Owner-User-Id 可伪造 仅平台后端持 token终端走平台自己的认证
绝对 memory path 显式共享 memory UI / docs 明确提示 opt-out 风险

相关文件

文件 职责
app/gateway/auth_middleware.py 全局认证门、JWT 严格验证、写入 user context
app/gateway/csrf_middleware.py CSRF double-submit 和 auth Origin 校验
app/gateway/routers/auth.py initialize/login/register/logout/me/change-password + SSO OIDC 端点providers/oauth/callback
app/gateway/auth/jwt.py JWT 创建与解析
app/gateway/auth/oidc.py OIDC 核心服务discovery、token exchange、ID token 验证、userinfo
app/gateway/auth/oidc_state.py OIDC state 管理signed cookie 存储 state/nonce/code_verifier
app/gateway/auth/user_provisioning.py OIDC 用户自动创建、email linking、domain 限制
app/gateway/auth/models.py 用户数据模型(含 oauth_provider / oauth_id
packages/harness/deerflow/config/auth_config.py OIDC 配置模型OIDCProviderConfig / OIDCAuthConfig
app/gateway/auth/reset_admin.py 密码 reset CLI
app/gateway/auth/credential_file.py 0600 凭据文件写入
app/gateway/authz.py 路由权限与 owner check
deerflow/runtime/user_context.py 当前用户 ContextVar 与 AUTO sentinel
deerflow/persistence/thread_meta/ thread metadata owner filter
deerflow/config/paths.py per-user filesystem layout
deerflow/agents/middlewares/thread_data_middleware.py run 时解析用户线程目录
deerflow/agents/memory/storage.py per-user memory storage
deerflow/config/agents_config.py per-user custom agents
app/channels/manager.py IM channel 内部认证调用与 owner header
app/gateway/internal_auth.py Internal Auth header 常量、token 校验、合成用户
scripts/migrate_user_isolation.py legacy 数据迁移到 per-user layout
.deer-flow/data/deerflow.db 统一 SQLite 数据库,包含 users / threads_meta / runs / feedback 等表
.deer-flow/users/{user_id}/agents/{agent_name}/ 用户自定义 agent 配置、SOUL 和 agent memory
.deer-flow/admin_initial_credentials.txt reset_admin 生成的新凭据文件0600读完应删除