Skip to content

请求生命周期与服务拓扑

本章目标:

  1. 把一个请求从浏览器到后端/引擎的每一跳讲清楚:谁鉴权、谁路由、谁注入身份头
  2. 看懂三类典型请求(页面访问、业务 API、Agent SSE)各走什么路径
  3. 理解 forward_auth 这道"集中鉴权前置"如何让所有上游服务无需各自实现登录

TL;DR

a-cdm 所有外部请求先过 Caddy。Caddy 对受保护路径做 forward_auth → acdm-backend:8002/auth/verify:verify 用 acdm_session cookie 解 JWT 查库,成功返 200 + X-Auth-User-Id/Email/Name/Role 四个头,Caddy 把这些头复制给上游再放行,失败原样返 401。前端 /workspace/*proxy.ts 守卫:无 cookie 直接 302 到 /api/acdm/auth/login 触发 Keycloak 登录。dev 不起 Caddy,由 proxy.ts 自己解 cookie 注入 X-Auth-* 头顶替 forward_auth,后端校验逻辑两边完全一致。

Overview(为什么用"集中 forward_auth"而不是每个服务各自鉴权)

a-cdm 有四个对外服务(前端、控制面、引擎 Gateway、引擎 Runtime、ERP)。如果每个服务各自实现"解析 cookie/token、查用户、判权限",会有三个问题:引擎是 vendored 代码不该塞 Keycloak 逻辑;鉴权实现散落多处难以保证一致;改鉴权要改 N 个服务。

a-cdm 的解法是 forward_auth 集中前置:Caddy 在把请求转给任何受保护上游之前,先同步调一次 acdm-backend 的 /auth/verify。verify 是唯一懂 Keycloak/JWT/用户表的地方,它把"你是谁"压缩成四个 X-Auth-* HTTP 头。上游服务(引擎、ERP)只需"信任并读取这些头",完全不碰鉴权逻辑。这就是为什么引擎里 acdm_auth.py 只做"从 header 提取身份"而不做"验证密码"——验证那步已经被 Caddy+verify 做完了。

Architecture:四类参与者

参与者职责关键文件Source
前端 proxy.ts/workspace/* 未登录守卫 + dev/`/api/langgraphmemory` header 注入deer-flow/frontend/src/proxy.ts
Caddy 网关路径反代 + forward_auth 鉴权前置deploy/prod/Caddyfile.keycloak-v1openspec/specs/architecture/spec.md:104-115
/auth/verify解 cookie 查库,产出 X-Auth-* 四头acdm-backend/app/auth/verify.pyacdm-backend/app/auth/verify.py:16-27
上游服务信任并消费 X-Auth-*acdm-backend / 引擎 / ERP

/auth/verify 实现极简(acdm-backend/app/auth/verify.py:16-27):Depends(get_current_user) 复用 cookie 解析 + DB 查询,成功就返 200 + 四个头:X-Auth-User-Id(user.id)、X-Auth-User-EmailX-Auth-User-NameX-Auth-User-Role("admin" 或 "user")。注释 verify.py:4 说明:401 时 Caddy 原封不动把 401 返客户端,前端 JS 跳 /auth/login

Data Flow:三类典型请求

请求 1:首次访问页面(未登录)

proxy.ts:82-89:PROTECTED_PATHS = ["/workspace"],无 acdm_session cookie → NextResponse.redirect("/api/acdm/auth/login")。后端接力到 Keycloak(详见鉴权章)。

请求 2:业务 API(已登录)

  1. 浏览器 GET /api/acdm/projects,带 acdm_session cookie。
  2. Caddy 命中 @acdm_api matcher,先 forward_auth → acdm-backend:8002/auth/verify
  3. verify 解 cookie 查库,返 200 + X-Auth-* 四头。
  4. Caddy copy_headers 把四头注入原请求,strip /api/acdm 前缀,反代 acdm-backend:8002
  5. acdm-backend 业务路由处理,get_current_user / 三级授权依赖再次校验(详见授权章)。

注意 /api/acdm/auth/* 不走 forward_auth(openspec/specs/architecture/spec.md:108)——登录入口本身不能要鉴权,否则死循环。

一个请求在鉴权维度上的状态机:

请求 3:Agent 流式对话(SSE)

  1. 前端发起 /api/langgraph/threads/{id}/runs/stream
  2. 生产:Caddy forward_auth → verify → 注入 X-Auth-* → strip /api/langgraph → 反代 deer-flow-langgraph:2024(Aegra)。
  3. dev:不起 Caddy,proxy.ts:77-78 命中 AUTH_INJECT_PREFIXES = ["/api/langgraph","/api/memory"],injectAuthHeaders() 从 cookie 解 JWT payload(parseJwtPayload base64url 解码,不验签,proxy.ts:40-55),注入 X-Auth-User-Id/X-Auth-User-Role,Next.js rewrite 直连 2024。
  4. 引擎 acdm_auth.authenticate(headers)X-Auth-User-Id 提取身份,做 owner_sub 隔离。
  5. Aegra 流式产出,SSE 持续推回前端;断线可 reattach(详见 AI 消息流章)。

关键不变量(proxy.ts:24):生产 dev 同路径,无后门。dev 的 proxy.ts 只是搬运身份头顶替 Caddy 这一跳,后端 acdm_auth 校验逻辑两边一字不差。

Implementation:proxy.ts 三职责

deer-flow/frontend/src/proxy.ts 是 Next.js 16 的请求前置(从 middleware.ts 重命名,导出函数名必须是 proxy,proxy.ts:1-4),config.matcher(proxy.ts:92-99)只拦 /workspace/*/api/langgraph/*/api/memory*:

职责触发路径行为Source
未登录守卫/workspace/*无 cookie → 302 /api/acdm/auth/loginproxy.ts:81-89
langgraph header 注入/api/langgraph/*解 cookie JWT → 注入 X-Auth-User-Id/Roleproxy.ts:57-79
memory header 注入/api/memory*同上(UI memory 标签直连 gateway:8001)proxy.ts:19-23 :77

injectAuthHeaders(proxy.ts:57-71)的容错:无 cookie / payload 无 sub 时 NextResponse.next() 原样放行,后端因缺头返 401(memory 路径则只是返回空 memory)——不在前端造假身份,验签责任永远在后端。

Common Pitfalls / 实战 Tips

  • /api/acdm/auth/* 必须排除 forward_auth:Caddy 配置里这条 matcher 要在 /api/acdm/* 之前,否则登录请求也要鉴权 → 永远登不进去。
  • proxy.ts 不验签是有意的(proxy.ts:16-17):它只搬运,验签在 acdm-backend。别在 proxy 里加 JWT 验签"加固",那是职责错位。
  • dev 缺 cookie 时 chat 坏:proxy.ts:14 注明,dev 不注入头则 acdm_auth.authenticate 因缺 header raise 401,dev chat 完全不可用 —— 这正是 proxy 第 2 职责存在的原因。
  • forward_auth 是同步阻塞子请求:每个受保护请求都多一次 verify 往返,verify 必须快(它只解 cookie + 一次 DB 查)。

References

  • acdm-backend/app/auth/verify.py:1-28 — forward_auth verify 端点(产出 X-Auth-* 四头)
  • deer-flow/frontend/src/proxy.ts:1-99 — 前端三职责守卫/注入(本章主源)
  • deploy/prod/Caddyfile.keycloak-v1 — Caddy 路由 + forward_auth 配置
  • openspec/specs/architecture/spec.md:104-115 — 反代路由表与 forward_auth 矩阵
  • acdm-backend/app/auth/router.py/auth/login Keycloak 接力
  • deer-flow/backend/packages/harness/deerflow/auth/acdm_auth.py — 引擎侧从 X-Auth-* 提取身份
PageRelationship
系统整体架构本章是该章服务拓扑的单请求逐跳展开
鉴权与授权双层体系本章 verify/Keycloak 在该章详解完整登录流与授权
proxy 鉴权守卫与 API 反代本章 proxy.ts 三职责在该章从前端视角再展开
控制面与引擎的耦合契约本章 X-Auth-* 头如何被引擎消费在该章详解
本地开发环境搭建本章 dev 无 Caddy 的 header 注入对应该章 dev 鉴权

公司内部参考 · 由 claude-wiki-gen 基于源码自动生成的二次分析