# Logto 受保护 API Mock 联调设计 ## 背景 当前扩展已经具备基础的 Logto 登录能力: - popup 可触发登录和登出 - background 可通过 `@logto/chrome-extension` 获取 access token - 页面功能在未登录时会被 auth gate 拦住 但现阶段业务数据仍然来自页面站内接口,尚未真正验证“扩展拿到 Logto access token 后,请求受保护 API”这条链路。后续真实后端将由其他人提供,因此当前阶段的目标不是接入正式接口,而是先在本地完成一套可重复验证的模拟联调方案。 ## 目标 - 新增一个专用于 Logto 受保护 API 的扩展客户端。 - 扩展请求受保护 API 前,必须先通过 background 获取 access token。 - 请求时自动附加 `Authorization: Bearer ` 请求头。 - 提供一个本地 mock 受保护 API,用于验证扩展到后端的完整调用链路。 - 提供自动化测试,分别覆盖: - token 注入逻辑 - mock API 授权成功/失败行为 ## 非目标 - 不接入真实业务后端。 - 不做真实 JWT 签名校验、JWKS 拉取或资源权限判定。 - 不修改现有星图页面数据采集逻辑为正式后端模式。 - 不在本阶段设计复杂的 token 刷新监控或重试策略。 ## 方案对比 ### 方案 A:只做本地 mock 后端 - 直接搭一个假接口,扩展请求它并观察返回。 - 优点:最接近最终使用方式。 - 缺点:如果联调失败,不容易快速判断问题出在 token 注入还是 mock 服务本身。 ### 方案 B:只做代码级测试 - 不起本地服务,只用测试替身验证请求头是否带 token。 - 优点:实现快,定位问题直接。 - 缺点:无法证明完整链路可运行。 ### 方案 C:先代码级测试,再接本地 mock 后端 - 先用单元测试锁定 token 注入行为,再起 mock 服务完成真实联调。 - 优点:定位问题更快,同时保留完整链路验证。 - 缺点:改动稍多于单一方案。 推荐采用方案 C。 ## 架构设计 ### 1. 扩展受保护 API 客户端 新增一个独立客户端模块,职责如下: - 向 background 发送 `auth:get-access-token` 消息 - 读取返回的 access token - 使用该 token 请求指定后端地址 - 将接口成功、未授权、网络失败这三类结果转成明确错误 这个客户端不直接耦合星图 DOM,也不依赖 popup。它只负责“拿 token 并带 token 发请求”,这样后续替换正式后端时只需要调整接口地址和返回映射。 ### 2. Background 认证桥接 现有 background 已支持 `auth:get-access-token`。本次不改变登录主流程,只把它当作唯一 token 来源: - content script 不直接接触 Logto SDK - 所有受保护 API 请求都通过 background 提供 token 这样可以保持认证逻辑集中,符合 MV3 扩展的边界约束。 ### 3. 本地 mock 受保护 API 新增一个轻量本地服务作为测试后端,建议职责保持极小: - 暴露固定测试 endpoint,例如 `/api/mock/protected` - 检查请求头中是否存在 `Authorization` - 如果请求头形如 `Bearer <非空字符串>`,返回固定假数据 - 如果没有该头,返回 `401` 本阶段不要求验证 token 是否来自真实 Logto,只验证“扩展是否按约定附带了 Bearer token”。 ## 数据流 完整调用链路如下: 1. 扩展中的业务入口调用受保护 API 客户端 2. 客户端向 background 发送 `auth:get-access-token` 3. background 返回当前 access token 4. 客户端带上 `Authorization: Bearer ` 请求本地 mock API 5. mock API 校验请求头并返回假数据 6. 客户端将结果回传给调用方 失败分支: - 如果 background 没返回合法 token,客户端直接报错,不发请求 - 如果 mock API 返回 `401`,客户端将其识别为未授权错误 - 如果请求超时或网络失败,客户端抛出网络错误 ## 接口设计 ### 扩展内客户端接口 建议客户端提供单一入口,例如: - `loadProtectedMockData()` 内部行为: - 先取 token - 再发 GET 请求 - 返回结构化响应对象或抛出明确错误 后续替换真实后端时,可以保留同样入口,内部再切换到真实 endpoint。 ### Mock API 返回 成功时返回固定 JSON,例如: ```json { "ok": true, "source": "mock-protected-api", "message": "authorized", "receivedAuthHeader": "Bearer ..." } ``` 失败时返回: ```json { "ok": false, "error": "unauthorized" } ``` ## 错误处理 ### 无 token - 判定条件:background 未返回 `auth:token`,或 token 为空字符串 - 处理方式:立即抛错,提示当前未登录或 token 不可用 ### 未授权 - 判定条件:后端返回 `401` 或 `403` - 处理方式:抛出授权失败错误,供上层展示“请重新登录” ### 网络失败 - 判定条件:fetch 抛异常、连接失败、超时 - 处理方式:抛出网络错误,不吞掉异常原因 ## 实现边界 建议新增或调整如下模块: ### `src/content/market` 下新增受保护 API 客户端 - 不替换现有页面接口客户端 - 独立处理 mock 受保护 API 访问 - 便于后续把“页面抓取模式”和“后端接口模式”并行保留 ### `src/shared/auth-messages.ts` - 复用现有 `auth:get-access-token` - 若现有消息结构足够,则不新增消息类型 ### `scripts/` 或独立目录中的 mock 服务 - 提供本地测试服务启动脚本 - 默认监听本地固定端口 - 返回固定 JSON 结果 ## 测试策略 ### 单元测试 - 客户端请求前会先获取 access token - 拿到 token 后,请求头中包含 `Authorization: Bearer ` - token 缺失时不会发起 fetch - 接口返回 `401` 时抛出未授权错误 - 接口返回成功时正确解析 JSON ### 联调测试 - 启动本地 mock 服务后,带 token 请求能成功 - 不带 token 请求返回 `401` - 扩展客户端能读到 mock 返回的假数据 ## 手动验证 1. 构建并加载扩展 2. 在 popup 中完成 Logto 登录 3. 启动本地 mock API 4. 触发扩展中的受保护接口请求 5. 确认 mock API 收到 `Authorization: Bearer ` 6. 确认扩展端收到成功响应 ## 迁移到真实后端的路径 当真实后端可用时,仅需要替换以下内容: - mock API 基地址 - 具体 endpoint 路径 - 返回数据结构映射 - 若真实后端要求额外 scope,则补充 `auth-config` 中的 scopes 核心认证链路保持不变: - 仍由 background 提供 token - 仍由独立客户端附带 `Bearer` 请求头 - 仍按未授权和网络错误分别处理