外观
认证中心与桌面壳 — 技术方案
对应 PRD 原文。本文档定义"怎么做";"做什么"以 PRD 为准,两者冲突时以 PRD 为准并回头修本文。 状态:草案(待评审)
术语看不懂请直接跳到 第 13 章 名词解释——本文出现的 OAuth、令牌、吊销相关术语都在那里,且只讲它在本系统里的作用。
1. 背景
上一迭代把 IM 跑通了,但认证是长在 nexo-im-api 内部的:users / devices 两张表、登录接口、凭证签发全在 IM 里,凭证形态是每设备一枚不透明 token,鉴权靠 devices.token_hash 单行索引查询。
这套设计对单应用是够用的,但 Nexo 的形态是壳 + 多子应用(IM 之后还有文档中心、会议、agent)。继续沿用会有三个必然后果:
- 第二个子应用要认证,只能反向依赖 IM 拿用户,IM 变成事实上的认证中心
- 每个子应用各自持有凭证,同一台机器在"登录设备"里会出现 N 条记录,强制下线要逐个踢
- 认证逻辑分散在各应用,安全问题要改 N 处
本期把认证抽成独立服务 nexo-auth,nexo-im-api 降为资源服务。
为什么是现在:迁移涉及 users / devices 两张表,当前只有 5 个种子账号、零真实业务数据,是整个生命周期里迁移成本最低的窗口。
2. 需求分析
从 PRD 的功能需求推导出的技术命题:
| PRD | 技术命题 | 关键约束 |
|---|---|---|
| FR-1 独立认证中心 | 新服务 + 独立 database,自研 OAuth 2.1 最小子集 | 禁止跨库 join;IM 不再持有任何认证凭据 |
| FR-2 唯一登录页 + SSO | 授权码 + PKCE;壳内壳外两条取码路径 | iframe 内第三方 cookie 被禁,子应用不能重定向 |
| FR-3 一台设备一个会话 | 壳持有 device,子应用共享 | 设备列表必须一台机器一条 |
| FR-4 ≤15min 访问令牌 + 5s 吊销 | 本地验签 + 吊销事件推送 + 主动断连 | 仅靠令牌过期无法满足 5s,必须有推送通道 |
| FR-5 IM 降为资源服务 | JWKS 本地验签 + 内存吊销集合 | IM 仍需用户昵称头像 → 保留只读投影 |
| FR-6 双平台桌面包 | Tauri + 本地回环回调 + 系统凭证库 | 回调白名单需放行 127.0.0.1:* |
| FR-7 统一日志 | 通用 SDK 统一提供 logger | 日志不得含明文密码与完整令牌 |
最硬的一条:5s 内强制下线。IM 的 WebSocket 在握手时验一次令牌,之后连接常驻。即使令牌立刻失效,已建立的连接也不会自己断开。所以 5s 生效的实现载体是服务端主动断连,不是令牌过期——这决定了必须存在 nexo-auth → nexo-im-api 的吊销通道。
3. 产品方案
一句话:认证中心托管唯一登录页,壳与 IM 都成为它的 client;一台设备一个会话,壳持有会话并向壳内子应用下发访问令牌。
- 用户在任意入口触发登录 → 跳转认证中心 → 登录一次 → 之后打开其他应用免登(SSO)
- 用户在「登录设备」里能看到自己所有在线设备,可远程踢掉任意一台,被踢端 5 秒内回到登录页
- 桌面端(macOS / Windows)用系统浏览器登录,密码不经过应用本体,凭证存系统凭证库
4. 相关资料
- PRD 原文 —— 本期需求与验收范围
- 验收用例 —— 验收标准(技术方案定稿后编写)
- 上一迭代 PRD —— 现有 IM 能力与遗留欠账
nexo-im-api/.trellis/tasks/archive/2026-08/08-11-mvp1-backend/design.md—— 现有后端方案(凭证、seq、WS 连接管理)- RFC 9700《OAuth 2.0 安全最佳实践》—— 授权码 + PKCE、刷新令牌轮换与复用检测的依据
- RFC 8252《OAuth 2.0 for Native Apps》—— 桌面端必须用系统浏览器 + 本地回环回调的依据
5. 参与人
| 角色 | 人员 | 职责 |
|---|---|---|
| 需求 / 验收 | 待填 | PRD 定稿、验收会主持、判定通过与否 |
| 研发 A | 待填 | 待评审时按模块认领 |
| 研发 B | 待填 | 待评审时按模块认领 |
| 评审 | 全员 | 08-18 上午技术方案评审 |
评审时把模块与人对应关系填进本表,再拆 issue 挂 org project。
6. 整体设计
6.1 组件与依赖
6.2 职责边界
| 组件 | 拥有 | 不碰 |
|---|---|---|
nexo-auth | 用户、密码、设备会话、刷新令牌、授权码、签名密钥 | 业务数据 |
nexo-im-api | 会话、消息、用户资料只读投影 | 密码、刷新令牌、令牌签发 |
| 壳 | 设备身份、刷新令牌、访问令牌的获取与分发 | 业务逻辑 |
| 子应用 | 业务 UI 与业务接口调用 | 刷新令牌、登录流程细节 |
单向依赖:IM 依赖 auth(拉公钥、同步资料),auth 只通过 webhook 反向通知,不读 IM 的任何数据。
6.3 令牌形态
| 访问令牌 | 刷新令牌 | |
|---|---|---|
| 形态 | JWT(EdDSA / Ed25519 签名) | 不透明随机串(256 bit) |
| 有效期 | 15 分钟 | 30 天,滑动续期 |
| 校验 | 资源服务本地验签,不回调 auth | 仅 auth 校验,查库 |
| 存储 | 只在内存 | 桌面端系统凭证库 / Web 端 localStorage |
| 载荷 | sub(用户) sid(设备) aud exp iat jti | 无载荷 |
为什么访问令牌里必须带 sid(设备标识):吊销的粒度是设备而非用户。没有 sid,IM 收到吊销通知后无法判断该拒绝哪些请求、该断开哪条连接。
7. 模块设计
7.1 数据模型变更
变更集中在两个 database,逐表标明动作:
nexo_auth(本期新建 database)
| 表 | 动作 | 说明 |
|---|---|---|
users | 新增(数据从 nexo_im.users 迁入) | 结构不变:id / username / nickname / avatar_url / password_hash / 时间戳 |
devices | 新增(数据从 nexo_im.devices 迁入,结构改造) | 删列 token_hash(不透明 token 模型作废);加列 client_id(哪个 client 发起的登录)、platform(web/macos/windows)、revoked_reason |
refresh_tokens | 新增(全新) | 刷新令牌轮换与复用检测的载体,见下 |
oauth_clients | 新增(全新) | client 注册表:client_id / name / redirect_uris(数组) / is_public |
authorization_codes | 新增(全新) | 授权码一次性消费:code_hash / client_id / user_id / device_id / code_challenge / redirect_uri / expires_at / consumed_at |
签名密钥不建表:本期单把 Ed25519 密钥经环境变量注入,kid 一并配置,JWKS 端点从内存产出。密钥轮换需要"新旧公钥并存一段时间",属于后续迭代,现在建表是为想象中的需求付成本。
refresh_tokens 为什么单独建表而不是在 devices 上加一列:复用检测要求能识别出"这个刷新令牌是曾经用过的旧代"。只存当前令牌哈希,旧令牌被重放时只能得出"不匹配",无法区分是攻击重放还是随便一个乱串。独立成表后,命中一条 used_at IS NOT NULL 的记录即可判定为重放,进而吸销该设备整条会话。
nexo_im(现有 database)
| 表 | 动作 | 说明 |
|---|---|---|
users | 修改(重命名为 user_profiles) | 删列 password_hash;加列 synced_at(上次从认证中心同步的时间)。保留 id / username / nickname / avatar_url,id 与认证中心的 users.id 同值 |
devices | 删除 | 迁往 nexo_auth,IM 侧不再保留 |
conversations | 不变 | 外键随 users → user_profiles 重命名自动跟随 |
messages | 不变 | 同上 |
IM 为什么还留一张用户表:渲染会话列表与消息气泡需要昵称头像。若每次都回调认证中心,认证中心就成了 IM 每个请求的同步依赖,与"单向依赖"的设计相悖,且 auth 抖动会直接让 IM 不可用。保留只读投影后,conversations / messages 的外键约束也得以维持。投影里没有任何凭据,不违反"IM 不持有认证数据"。
7.2 认证中心:接口清单
| 方法 | 路径 | 用途 | 鉴权 |
|---|---|---|---|
| GET | /.well-known/jwks.json | 公钥集,供资源服务验签 | 公开 |
| GET | /authorize | 授权端点:无会话则渲染登录页,有会话直接发码 | 会话 cookie(可无) |
| POST | /login | 登录页表单提交 | 无 |
| POST | /token | 授权码换令牌 / 刷新令牌换令牌 | PKCE 或刷新令牌 |
| POST | /logout | 结束当前设备会话 | 访问令牌 |
| GET | /devices | 当前用户的登录设备列表 | 访问令牌 |
| DELETE | /devices/:id | 吊销指定设备 | 访问令牌 |
| GET | /internal/users | 按 id 批量取用户资料,供 IM 同步投影 | 服务间密钥 |
| GET | /internal/revocations | 全量有效吊销列表,供资源服务启动时恢复 | 服务间密钥 |
/internal/* 的鉴权与暴露面
服务间接口用共享密钥 + HMAC 签名,不走用户令牌。签名串必须包含时间戳,服务端只接受 ±60 秒内的请求并拒绝重复的 nonce——不做防重放的话,抓到一次 /internal/users 请求即可无限重放刷取用户资料。
不引入 mTLS 的理由不是"规模小",而是部署拓扑:两个服务在同一台主机、同一个 docker compose 内,调用流量走 bridge 网络,从不离开本机。mTLS 防的是网络路径上的中间人,而这条路径上没有网络。能嗅探 docker bridge 的攻击者已经拿到宿主机 root,那时直接读环境变量里的签名私钥即可,mTLS 拦不住。代价一侧则是自建 CA、证书分发、以及"到期即全站服务间调用中断且平时毫无征兆"的定时炸弹。
这条结论随拓扑失效:一旦 nexo-auth 与 nexo-im-api 拆到不同主机,流量要过真实网络,共享密钥裸传就不够了——届时至少上 TLS,mTLS 才谈得上必要。
比 mTLS 更该先堵的是暴露面:nexo-im-api 绑在 127.0.0.1:3000,前置反向代理转发。若反代配成 location / 全量转发,/internal/* 会直接从公网可达——此时有没有 mTLS 是次要的,这组路由根本不该出现在公网。反代必须显式拒绝 /internal/ 前缀,该配置写入部署文档并在上线后实测验证。
7.3 登录:独立访问路径(授权码 + PKCE)
子应用不在壳内时,自己就是 client,走标准重定向。
SSO 由会话 cookie 实现:第二个 client 再次进入 /authorize 时携带同域 cookie,跳过登录页直接发码。这也是"一次登录、处处可用"的唯一机制。
授权码必须一次性:consumed_at 非空即拒绝。重复兑换是典型的攻击信号,同时吸销对应设备。
7.4 壳内路径:message channel 通信
iframe 内第三方 cookie 已被浏览器禁用,子应用不能在壳内重定向。壳完成登录后通过 postMessage 下发令牌。
两条路径对子应用透明:前端 SDK 只暴露 getAccessToken(),内部判断 window.self !== window.top 决定走 postMessage 还是走自己的 OIDC 流程。子应用代码里不出现任何分支。
握手时序陷阱:子应用命中缓存秒开时,app:ready 可能早于壳注册监听。现有实现已用"先注册监听、再设置 iframe src"规避(见 nexo-app/src/features/workbench/pages/workbench-page.tsx 的注释),本期沿用该顺序。
待评审确认点:现有事件名用 im: 前缀(im:ready / im:unread),面向多子应用应改为 app: 前缀。本期只有 IM 一个子应用,现在改成本最低,但不在 PRD 范围内——请评审时决定是否顺手改掉。上图按已改写。
7.5 桌面端:本地回环回调
桌面应用不能把登录页开在应用内嵌窗口(违反 RFC 8252,且拿不到系统浏览器已有的 SSO 会话)。
认证中心侧的配套改动:oauth_clients.redirect_uris 对桌面 client 需放行 http://127.0.0.1:*,端口部分做通配。这是 RFC 8252 明确允许的例外——仅对回环地址生效,其余 client 一律精确匹配。
密码不经过应用本体:输入发生在系统浏览器,Tauri 只拿到授权码。
7.6 令牌续期与刷新令牌复用检测
为什么重放要吸销整条会话而不是只拒绝这一次:刷新令牌是滚动的,旧令牌出现在网络上只有两种可能——客户端实现有 bug,或令牌已泄露。两种都不该让会话继续存活。宁可让用户重登一次。
7.7 强制下线:5 秒内生效
这是本期最硬的指标,实现载体是服务端主动断连。
时间预算:吊销写库与 webhook 投递均为同步调用,正常链路在百毫秒级完成,5 秒指标有充足余量。webhook 失败时的兜底见 7.8。
为什么本地验签通过还要拒绝:访问令牌最长还有 15 分钟有效期,仅靠验签无法感知吊销。内存吊销集合是必需的第二道判定。集合按 sid 索引,条目在对应令牌自然过期后清理。
7.8 认证降级
降级时的指标退化必须说清楚:webhook 正常时强制下线 ≤5 秒;webhook 全部失败、仅靠轮询兜底时,最坏为 60 秒。这条要在验收时讲明——5 秒是正常链路的承诺,不是故障链路的承诺。
明确不做的降级:认证中心不可用时不启用"离线放行"(即不因为验不了签就跳过鉴权)。宁可拒绝,不可放行。
7.9 IM 资源服务改造
| 改造点 | 做法 |
|---|---|
| 鉴权中间件 | 从"查 devices.token_hash"改为"JWKS 本地验签 + 内存吊销集合校验" |
| 公钥获取 | 启动时拉 JWKS 并缓存,按 kid 索引;拉取失败不阻塞启动,用上次缓存 |
| 吊销集合恢复 | 启动时调 GET /internal/revocations 全量拉取,避免重启后吊销状态丢失导致已踢设备复活 |
| 用户资料同步 | 首次遇到未知 sub 时按需拉取并写入 user_profiles;synced_at 超过 24 小时的异步刷新 |
| 设备列表接口 | IM 侧下线,前端改调认证中心 |
| 登录 / 登出接口 | IM 侧下线 |
| WebSocket 握手 | 校验方式同上;连接注册表的键从 token 改为 sid,以便按设备定位并断开 |
重启窗口是必须堵的洞:吊销集合只在内存里,若不做启动全量拉取,nexo-im-api 一次重启就会让所有已吊销设备恢复访问,直到它们的访问令牌自然过期。
8. 排期
开发窗口 08-18 至 08-22,08-22 下午留给联调与预跑验收用例。模块与人的对应关系待评审时认领。
| 日期 | 后端线 | 前端线 | 交汇点 |
|---|---|---|---|
| 08-18 | 评审后开工:nexo-auth 骨架、oauth_clients / 表结构、迁移脚本 | 前端 SDK 契约落地(getAccessToken() 与桥接事件) | 上午评审时钉死接口契约,之后两条线才能并行 |
| 08-19 | 授权码 + PKCE + /token + JWKS | 壳改为正式 client,走通独立访问的重定向登录 | 认证中心可联调 |
| 08-20 | 刷新轮换与复用检测、设备列表与吊销、webhook | 壳内 postMessage 下发与主动重发;子应用去掉本地登录表单 | 端到端首次打通 |
| 08-21 | nexo-im-api 改造:验签中间件、吊销集合、资料同步 | Tauri 双平台:回环回调、系统凭证库、CI 构建矩阵 | IM 可用新令牌访问 |
| 08-22 上午 | 统一日志接入、部署与分发文档 | 桌面包产出并分发到团队 | 全链路联调 |
| 08-22 下午 | 预跑 TC,离线核验项出证据 | 同左 | 缺陷修复 |
| 08-23 | 验收会 45 分钟 + 15 分钟定下期 |
08-18 上午的评审是硬前置:接口契约(令牌载荷字段、桥接事件名与结构、webhook 报文、服务间鉴权方式)不在评审时定死,两条线当天就会互相阻塞。
9. 发布计划
按依赖顺序分五步,每步独立可回滚。
| 步骤 | 动作 | 影响面 | 回滚 |
|---|---|---|---|
| 1 | 部署 nexo-auth(新 compose 服务 + 新 database + 反代与域名) | 无,现网不感知 | 停掉容器即可,无残留 |
| 2 | 执行数据迁移:users / devices 导入 nexo_auth | 需要停写窗口,预计分钟级 | 保留迁移前快照,回滚脚本恢复 |
| 3 | 发布 nexo-im-api 改造版;nexo_im 执行表变更(users 改名、删 devices) | 现有登录态全部失效,用户需重登 | 镜像回退上一 tag + 反向迁移脚本 |
| 4 | 发布壳与 nexo-im-pc 的新前端 | 前端整体切到新登录流程 | Pages 回滚到上一次部署 |
| 5 | 分发双平台桌面包 | 新增能力,无破坏性 | 撤下下载链接 |
必须公告的一点:第 3 步会让所有人当场掉线并需要重新登录。这是凭证模型从不透明 token 换成 JWT 的必然结果,无法平滑过渡。发布前在群里说明。
发布顺序不可颠倒:前端先发会指向尚不存在的认证中心接口;IM 先改会拒绝所有旧凭证但用户还没有新的取得途径。
10. 稳定性保障
| 关注点 | 措施 |
|---|---|
| 认证中心成为单点 | 资源服务本地验签,不把 auth 放进每请求的同步路径;auth 短时不可用不影响已登录用户的业务操作 |
| JWKS 拉取失败 | 启动时拉取失败不阻塞启动,使用上次缓存的公钥;运行期定时刷新,失败仅告警 |
| 吊销通知丢失 | 双通道:webhook 实时(指数退避重试 3 次)+ 每 60 秒增量轮询兜底 |
| 服务重启导致吊销状态丢失 | 启动时全量拉取有效吊销列表后再开始接受流量 |
| 数据迁移出错 | 迁移脚本先在本地与预发各跑一次;保留迁移前快照与反向脚本;种子账号可重建,无真实业务数据 |
| 自研授权流程的安全实现 | 评审时逐项对照自查:授权码一次性、回调地址精确匹配(回环端口除外)、PKCE 校验、state 绑定、刷新轮换与复用检测 |
| 可观测性 | 统一日志覆盖登录成败、授权码签发与兑换、令牌续期、设备吊销、webhook 投递结果、WS 建连与断开六类事件,均可关联到用户与设备 |
| 回环端口被占用或被安全软件拦截 | 端口随机化并重试;失败时提示用户改用网页版登录 |
| 密钥管理 | 签名私钥与服务间共享密钥仅存服务端环境变量,不入库不入镜像;日志与错误响应中禁止出现密钥、完整令牌、明文密码 |
/internal/* 暴露到公网 | 反向代理显式拒绝 /internal/ 前缀;写入部署文档,上线后从公网实测一次确认返回 404/403 而非 200 |
| 服务间请求被重放 | HMAC 签名串含时间戳与 nonce,服务端只接受 ±60 秒窗口内的请求并拒绝重复 nonce |
11. 风险
| 风险 | 影响 | 应对 |
|---|---|---|
| 认证链路未按期打通,桌面包挤占时间 | 主线交付不了 | 桌面壳降级为"仅加载远程 Web 壳",放弃回环回调与凭证库,认证链路优先 |
| CI 构建矩阵某一平台卡住 | 单平台包缺失 | 优先保证团队成员各自主力系统可用,卡住的平台单独降级到 0830,不拖累整条 FR-6 |
| 自研授权码流程存在安全缺陷 | 越权风险 | 评审自查清单 + 验收用例覆盖非法回调、错误密码、令牌重放 |
| 迁移导致账号不可用 | 全员无法登录 | 迁移前快照 + 回滚脚本;发布窗口安排在低峰 |
| 5 秒指标在故障链路下达不到 | 验收判定争议 | 方案已写明:5 秒是 webhook 正常时的承诺,轮询兜底为 60 秒,验收前向验收人说明 |
| 桥接事件前缀是否改名未决 | 前后端实现不一致 | 列为评审确认点,评审当场定 |
| 未做代码签名 | Windows 安装出现 SmartScreen 警告 | PRD 已列入明确不做,验收口径已在 TC 中写明 |
12. 待评审确认点
评审时必须逐条给出结论,否则开工即阻塞:
- 桥接事件前缀:
im:改为app:与否(见 7.4) - 访问令牌载荷字段:除
sub/sid/aud/exp/iat/jti外是否还需要username等便利字段(带上可省一次资料查询,但令牌体积变大且资料变更后令牌内是旧值) - 服务间鉴权:认可"同主机拓扑下不上 mTLS"的判断吗(见 7.2)。若认可,则本期必须落实两项配套——反代拒绝
/internal/前缀、HMAC 带时间戳与nonce防重放;若不认可,需要为证书签发与轮换单独排期 - 访问令牌有效期:15 分钟是否合适(越短越安全,但续期请求越频繁)
- 迁移停写窗口:安排在哪个时间点,是否需要提前公告
- 参与人:第 5 节表格的模块认领
13. 名词解释
本章解释文中出现的术语。只写它在本系统里的含义与作用,不做通用词典式定义。
13.1 认证与授权流程
| 术语 | 含义 |
|---|---|
| 认证中心 | 本期新建的 nexo-auth 服务。全生态唯一持有用户密码、签发凭证的地方 |
| 资源服务 | 只提供业务数据、不签发凭证的服务。本期 nexo-im-api 从"自己管认证"降级为资源服务 |
| client | 向认证中心申请凭证的应用。本期有三个:壳的 Web 版、壳的桌面版、独立访问的 nexo-im-pc |
| 授权码模式 | 客户端先拿一张一次性"取货码"(授权码),再用它到令牌端点换令牌。好处是令牌不经过浏览器地址栏,不会落进历史记录与日志 |
| PKCE | 授权码模式的防截获补丁。客户端先自己造一个随机串 code_verifier,把它的哈希 code_challenge 先交给认证中心;换令牌时出示原串。攻击者即使截到授权码,拿不出原串也换不到令牌 |
| state | 发起授权时带上的随机串,回调时原样带回并比对。用于确认"这次回调确实是我发起的那次",防跨站请求伪造 |
| SSO | 单点登录。本系统靠认证中心域下的会话 cookie 实现:登录过一次后,其他 client 进入授权端点时带着该 cookie,直接发码、不再要密码 |
| 授权端点 / 令牌端点 | /authorize 与 /token。前者负责"确认你是谁并发码",后者负责"拿码换令牌",职责分开是为了让密码只出现在前者 |
13.2 令牌与密钥
| 术语 | 含义 |
|---|---|
| 访问令牌 | 调业务接口时出示的短期凭证,本系统为 15 分钟有效的 JWT。过期即失效,因此泄露的危害窗口很小 |
| 刷新令牌 | 用来换取新访问令牌的长期凭证,30 天。它是真正需要保护的那一枚,只存在客户端的安全存储里,子应用永不接触 |
| JWT | 一种自带签名的令牌格式。资源服务用公钥验签就能确认它没被篡改,不需要回头问认证中心——这是本方案能避免认证中心成为每请求依赖的根本原因 |
| 载荷 / claims | JWT 里携带的字段。本系统用到 sub(用户)、sid(设备)、aud(受众)、exp(过期时间)、iat(签发时间)、jti(令牌唯一编号) |
sid | 设备标识。吊销的粒度是设备而非用户,没有它资源服务就不知道该拒绝哪些请求、该断开哪条连接 |
| JWKS | 认证中心发布公钥的标准端点(/.well-known/jwks.json)。资源服务拉一次缓存起来即可验签;新应用接入时指向同一个地址,零配置 |
kid | 密钥编号。JWT 头部带上它,验签方据此在 JWKS 里挑对应公钥——这是将来能平滑轮换密钥(新旧公钥并存)的前提 |
| EdDSA / Ed25519 | 本系统采用的非对称签名算法。私钥签、公钥验,公钥可以公开发布 |
| 刷新令牌轮换 | 每次续期都作废旧的刷新令牌、发一枚新的。目的是缩短单枚令牌的存活时间 |
| 复用检测 | 已被用过的旧刷新令牌再次出现时,判定为泄露或客户端缺陷,吸销该设备整条会话。这是轮换机制真正的价值所在——只轮换不检测,等于没防住 |
| 系统凭证库 | 操作系统提供的加密存储:macOS 钥匙串、Windows 凭据管理器。桌面端的刷新令牌存这里,不落明文文件 |
13.3 吊销与服务间通信
| 术语 | 含义 |
|---|---|
| 吊销 | 让某台设备的凭证立即失效。写库只是第一步,还要让资源服务知道 |
| 吊销集合 | 资源服务内存里的一份"已失效设备"名单。访问令牌验签通过后还要过这道判定——因为令牌本身最长还有 15 分钟有效期,光验签感知不到吊销 |
| webhook | 认证中心主动向资源服务推送吊销事件的 HTTP 回调。5 秒生效指标靠它 |
| 轮询兜底 | webhook 投递失败时的第二条通道,每 60 秒增量拉取。它保证最终一致,但生效时间从 5 秒退化到 60 秒 |
| HMAC | 服务间调用的签名方式,双方共享一个密钥。签名串含时间戳与 nonce,服务端只接受 ±60 秒窗口内且未重复的请求 |
nonce | 一次性随机串,用于防重放——同一个 nonce 出现第二次即拒绝 |
/internal/* | 只允许服务之间调用的接口前缀,不面向用户。反向代理必须显式拒绝该前缀,否则会从公网可达 |
13.4 壳与子应用
| 术语 | 含义 |
|---|---|
| 壳 | nexo-app,承载各子应用的容器,有 Web 与桌面(Tauri)两种形态。它持有设备身份与刷新令牌 |
| 子应用 | 跑在壳里的独立业务应用,本期只有 IM,后续有文档中心、会议、agent |
| postMessage | 浏览器提供的跨窗口通信机制。壳与 iframe 里的子应用靠它传递登录态——因为 iframe 内第三方 cookie 已被浏览器禁用,子应用无法自行走重定向登录 |
| 前端 SDK | 供子应用调用的统一入口(getAccessToken())。内部判断自己是在壳内还是独立访问并自动分流,子应用代码里不出现这个分支 |
| 只读投影 | nexo_im.user_profiles。IM 渲染昵称头像需要用户资料,但不该反向依赖认证中心的每次请求,于是在本地存一份只含公开字段、不含任何凭据的副本 |
| Tauri | 用 Rust 打包桌面应用的框架,界面仍是 Web。本期用它产出 macOS 与 Windows 安装包 |
| 本地回环回调 | 桌面端登录时,应用在 127.0.0.1 上临时起一个只监听本机的小服务接收授权码。这样登录页能开在系统浏览器里(可复用已有 SSO 会话),而密码始终不经过应用本体 |