# Logto 登录集成设计 日期:2026-04-21 ## 1. 背景 当前项目是一个 Chrome MV3 扩展,主要运行在星图市场页面,通过内容脚本注入页面并提供筛选、排序、导出等增强能力。 下一阶段需要为插件补齐登录能力,接入 Logto,目标是: - 让插件具备统一的登录态 - 登录后可为未来的业务后端 API 获取 access token - 未登录时禁止使用当前插件能力 - 这一版先打通登录、登出、会话持久化和 token 获取链路,不接真实业务后端 ## 2. 已确认的产品决策 - 登录入口放在扩展 `popup` - 目标范围是“插件登录态 + 为未来后端 API 预留 access token 获取能力” - 当前版本暂不接真实后端 API - 未登录时,插件功能整体禁用 - 仅允许内部已有 Logto 账号成员登录,不开放注册 - `popup` 默认显示简洁账号信息,开发模式下显示调试信息 - 未登录时,市场页禁用面板需要提供“去登录”引导 - 配置项先使用占位值,不在设计文档里写死真实 tenant / appId / resource - 设计文档明确建议新增目录结构,而不是只写抽象功能 ## 3. 官方文档约束 本设计基于以下 Logto 官方文档: - Chrome 扩展快速开始: - Global API resources: 从官方文档抽取的关键约束如下: - Chrome 扩展应使用适配扩展场景的登录流程,重定向 URI 基于 `chrome.identity.getRedirectURL()` - Manifest 需要增加 `identity`、`storage` 等权限 - 登录流程更适合由后台脚本统一承接,而不是由内容脚本直接执行 - access token 应面向一个明确的 API resource,而不是把“登录成功”与“可访问后端 API”混为一谈 这些约束直接决定了当前项目不能把认证逻辑塞进现有内容脚本入口。 ## 4. 目标与非目标 ### 4.1 目标 - 在扩展 `popup` 中提供登录和登出入口 - 在 `background service worker` 中统一处理 Logto 认证 - 在内容脚本中基于登录态控制插件可用性 - 登录成功后可读取基础用户信息 - 为未来业务后端 API 预留 `getAccessToken(resource, scopes)` 能力 - 通过 TDD 落地实现,保证认证状态切换和权限门控可回归测试 ### 4.2 非目标 - 当前版本不接真实业务后端 API - 当前版本不支持开放注册 - 当前版本不做复杂账号中心页面 - 当前版本不要求多标签页实时广播登录态变化 - 当前版本不默认展示完整 token 明文 ## 5. 推荐架构 采用方案:`Background 认证中枢 + Popup 展示 + Content 只消费状态` ### 5.1 模块边界 #### `src/background/auth/*` 职责: - 初始化 Logto 客户端 - 执行 `signIn` - 执行 `signOut` - 执行 `getAuthState` - 执行 `getAccessToken` - 统一收敛认证错误 约束: - 它是唯一直接接触 Logto SDK 和登录流程的层 - 内容脚本和 popup 都不直接复制认证流程 #### `src/popup/*` 职责: - 作为唯一登录入口 - 展示未登录态、已登录态、错误态 - 在开发模式下显示调试信息 - 向 background 发送认证消息 #### `src/content/*` 职责: - 页面启动时查询认证状态 - 未登录时渲染禁用态工具栏 - 已登录后再初始化现有筛选、排序、导出能力 约束: - 不直接依赖 Logto SDK - 不自己发起登录 - 不长期缓存 access token #### `src/shared/auth-messages.ts` 职责: - 定义 popup / content / background 之间的消息协议 - 避免认证消息散落在多个文件中 ### 5.2 为什么这样分层 - 符合 Logto 官方 Chrome 扩展接法 - 符合 MV3 生命周期特征,避免把认证流程绑死在不稳定的内容脚本生命周期上 - 后续接业务后端时只需要扩展 background 的 token 获取逻辑 - 权限边界清晰,未登录时不会出现“部分功能还能偷偷使用”的行为 ## 6. 推荐目录结构 建议在当前项目中新增以下结构: ```text src/ background/ auth/ client.ts controller.ts state.ts types.ts index.ts popup/ index.html index.ts view.ts types.ts shared/ auth-messages.ts auth-config.ts content/ market/ auth-gate.ts ... ``` 目录约束: - `background/auth/*` 只做认证中枢,不掺杂市场业务逻辑 - `popup/*` 只处理展示和用户交互 - `shared/*` 放跨入口共享的数据结构和配置解析 - `content/market/auth-gate.ts` 只做登录态门控,不直接写登录流程 ## 7. 配置设计 当前版本只定义占位配置,不写死真实值。 建议新增 `src/shared/auth-config.ts`,集中管理以下配置: ```ts export interface AuthConfig { logtoEndpoint: string; appId: string; apiResource: string; scopes: string[]; enableDevAuthPanel: boolean; } ``` 建议占位字段: - `logtoEndpoint`: `https://.logto.app` - `appId`: `` - `apiResource`: `` - `scopes`: `["openid", "profile", "offline_access"]` - `enableDevAuthPanel`: `false` 配置要求: - 缺失配置时必须明确报错 - 不允许 silent fail - 后续真实接入时应能通过单点改动切换环境 ## 8. Manifest 变更要求 现有 manifest 仅覆盖下载和内容脚本注入,接入 Logto 后至少需要补充: - `permissions` 增加 `identity` - `permissions` 保留或补充 `storage` - 新增 `action.default_popup` - 根据实际请求域名增加必要的 `host_permissions` 示意要求: ```json { "permissions": ["downloads", "identity", "storage"], "action": { "default_popup": "popup/index.html" } } ``` 实际值需要结合构建输出路径调整,但规范上必须支持 popup 与 identity 登录流程。 ## 9. 认证与会话数据流 ### 9.1 登录流程 1. 用户点击扩展图标,打开 `popup` 2. `popup` 读取 background 返回的 `auth state` 3. 如果未登录,点击“登录 Logto” 4. `popup` 发送 `auth:sign-in` 给 background 5. background 发起 Logto 登录流程 6. 登录成功后,background 更新本地认证摘要状态 7. `popup` 重新读取状态并渲染已登录信息 ### 9.2 内容脚本门控流程 1. 内容脚本进入星图市场页 2. 插件初始化前先发送 `auth:get-state` 3. 如果未登录,渲染禁用态,不执行当前业务能力初始化 4. 如果已登录,继续执行现有注入逻辑 ### 9.3 登出流程 1. 用户在 `popup` 点击退出登录 2. `popup` 发送 `auth:sign-out` 3. background 清理会话 4. `popup` 回到未登录态 5. 已打开的市场页在刷新后进入禁用态 ### 9.4 token 策略 当前版本采用: - Authorization Code Flow + PKCE - 基础 scopes:`openid profile offline_access` - `resource` 对应未来业务后端 API 的 global API resource 约束: - `ID token` 仅用于身份展示 - `Access token` 仅用于未来业务后端 API 调用 - 内容脚本只按需向 background 请求 token,不本地长期存储 ## 10. 消息协议建议 建议新增一组显式消息类型: ```ts type AuthMessage = | { type: "auth:get-state" } | { type: "auth:sign-in" } | { type: "auth:sign-out" } | { type: "auth:get-access-token" }; ``` 建议的状态结构: ```ts interface AuthState { isAuthenticated: boolean; userInfo: { sub: string; name?: string; username?: string; email?: string; } | null; scopes: string[]; resource: string | null; accessTokenExpiresAt: number | null; lastError: string | null; } ``` 设计要求: - 状态必须可序列化 - 消息响应必须有稳定结构 - 失败时返回明确错误码或错误文案 ## 11. Popup 交互设计 ### 11.1 未登录态 显示内容: - 插件名称 - 说明文案:“登录后才能使用星图增强功能” - 主按钮:“登录 Logto” - 最近一次登录失败摘要(如果存在) ### 11.2 已登录态 默认展示: - 已登录状态 - 用户名 - 邮箱 - `退出登录` 按钮 ### 11.3 开发调试态 仅在 `enableDevAuthPanel` 开启时显示: - `isAuthenticated` - `sub` - `scopes` - `resource` - token 是否可获取 - access token 预计过期时间 - 最近一次认证错误 - 手动“刷新状态”按钮 默认不显示完整 token 字符串。 ## 12. 内容脚本权限门控设计 ### 12.1 未登录态 插件工具栏应替换为禁用面板,显示: - 主提示:“请先登录插件” - 次提示:“打开扩展弹窗完成登录后刷新本页” - 引导按钮:“去登录” “去登录”按钮行为: - 不直接尝试从内容脚本打开 popup - 仅提示用户点击浏览器工具栏中的扩展图标完成登录 未登录时: - 不初始化筛选 - 不初始化排序 - 不初始化导出 - 不发起后续业务请求 ### 12.2 已登录态 保持当前市场增强行为不变。 ### 12.3 登录失效态 如果内容脚本运行过程中发现 background 返回未登录或 token 不可用: - 立即切回禁用态 - 提示“登录已失效,请重新登录” ## 13. 错误处理设计 至少覆盖以下错误: - Logto 配置缺失 - 登录取消 - 登录回调失败 - token 获取失败 - popup 无法读取 background 状态 - 内容脚本无法获取登录态 设计要求: - popup 中展示用户可理解的错误摘要 - background 中保留标准化错误结构 - 内容脚本遇到认证错误时优先进入禁用态,而不是继续执行业务逻辑 ## 14. TDD 实施要求 实现必须采用 TDD,遵循: 1. 先写失败测试 2. 明确验证测试因缺失功能而失败 3. 再写最小实现让测试通过 4. 最后做必要重构 不允许先写生产代码再补测试。 ## 15. 测试分层建议 ### 15.1 单元测试:消息协议与状态结构 建议文件: - `tests/auth-messages.test.ts` - `tests/auth-state-store.test.ts` 覆盖点: - 消息类型收发正确 - 状态序列化结构稳定 - 配置缺失时能返回明确错误 ### 15.2 单元测试:background 认证协调器 建议文件: - `tests/background-auth-controller.test.ts` 覆盖点: - `signIn` 调用认证入口 - `signOut` 清理会话 - `getAuthState` 返回认证摘要 - `getAccessToken` 在未登录或配置无效时失败明确 ### 15.3 单元测试:popup UI 建议文件: - `tests/popup-entry.test.ts` 覆盖点: - 未登录显示登录按钮 - 已登录显示用户信息和退出按钮 - 开发模式显示调试区 - 错误态显示错误摘要 ### 15.4 集成测试:内容脚本门控 建议文件: - `tests/market-auth-gating.test.ts` 覆盖点: - 未登录时市场页工具栏进入禁用态 - 已登录后才初始化现有筛选、排序、导出 - 运行中登录失效时切回禁用态 ## 16. 验收标准 - 扩展加载后存在可用的 `popup` - 未登录时 `popup` 显示登录入口 - 登录成功后 `popup` 显示基础用户信息 - 开发模式下 `popup` 能显示认证调试信息 - 未登录时市场页插件整体禁用 - 登录后刷新市场页,现有插件能力恢复 - 登出后刷新市场页,插件重新禁用 - 配置缺失时,popup / background / content 都能给出明确错误,而不是静默失败 ## 17. 后续实现顺序建议 建议实施顺序: 1. 补 manifest 与构建入口,先让 popup 可以独立加载 2. 写 `auth-messages` 与 `auth-config` 的失败测试 3. 写 background auth controller 的失败测试并落最小实现 4. 写 popup 状态渲染测试并接上 background 5. 写内容脚本门控测试,再把市场页逻辑接入认证态 6. 最后补开发调试面板 ## 18. 当前仍保留为占位的实施参数 这些值在真正开始编码前仍需由你提供: - Logto tenant endpoint - Logto Chrome extension appId - Global API resource 标识 - 是否存在额外自定义 scopes - 调试面板是否区分开发包与生产包 在这些真实值到位前,可以先完成目录搭建、消息协议、状态门控和测试骨架。