忘机山人
在第一章里,我们刻意做了一件事: **完整走完了一次“正常的懒猫 SSO 登录流程”,但没有解释任何安全细节。** https://appstore.lazycat.cloud/#/shop/detail/xu.deploy.env 如果你回看那个流程,会发现一个非常危险的现象: > 整个过程中,ENV 查看器几乎没有“主动验证”任何东西。 它只是: * 把浏览器重定向到 `/sys/oauth/auth` * 等待浏览器跳回 `/callback` * 拿到一个 `code` * 换回一组 token 然后,很多懒猫 App 就直接宣布: > **“登录成功。”** 这件事之所以看起来合理,是因为我们在脑海中默认了一整套“看不见的前提”。 而这一章的目的,就是把这些前提**全部显性化**。 --- ## 2.1 一个被默认的信任模型 当 ENV 查看器收到一个 `id_token` 时,通常会发生如下对话(虽然没有写在任何代码里): * 懒猫 SSO 说: > “这个 token 代表用户 alice。” * ENV 查看器心里想: > “好,那她就是 alice,还是盒子主人,那我给她管理员视图。” 这个逻辑的问题不在于“不合理”,而在于: > **它跳过了所有“为什么我应该相信你”的问题。** 在安全系统里,任何跳过“为什么”的设计,都会在后面以事故的形式补回来。 而在家庭服务器这种**主人和被邀请家人共用一个盒子**的场景下, “我该不该把 Alice 当成管理员”这个判断,一旦出错,后果不可逆。 --- ## 2.2 把隐含假设全部摊开 我们先不谈攻击者。 只从逻辑完整性出发,把 ENV 查看器在“信任登录结果”时,**隐含接受的假设列出来**: 1. 返回的 token 真的是**这台盒子上的** 懒猫 SSO 发的 2. token 在传输过程中没有被修改 3. token 不是别人伪造的 4. token 是发给 `xu.deploy.env` 这个 Client 用的,而不是发给盒子上**另一个应用**的 5. token 还在有效期内 6. token 对应的是刚才那次登录,而不是别的历史请求 7. 浏览器没有被篡改 8. 家庭内网没有被劫持 你会发现一个事实: > **流程中,并没有任何一步“天然保证”这些假设成立。** 换句话说: > **流程跑通 ≠ 假设成立** --- ## 2.3 为什么“看起来没问题”的系统依然不安全 很多懒猫 App 开发者第一次接触这个问题时,会产生一个非常自然的反应: > “这些假设在家庭内网里几乎总是成立的吧?我家又没有 APT。” 这正是问题的核心。 安全系统设计的第一原则是: > **不要基于“通常如此”来建立信任。** 尤其在懒猫这种场景下,“通常”其实比你想的更脆弱: * 盒子可能被暴露公网(很多人开了 `heiyu.space` 的公网访问) * 盒子上跑着**多个应用**,它们共享同一个懒猫 SSO * 被邀请的家人带进来的设备你无法控制 * 一些“炫技”级玩法(自签 SSL、自建反代、第三方 OIDC 对接)会破坏默认安全假设 OIDC 的设计,建立在一个非常保守的前提之上: > **假设浏览器、网络、中间环境,全部是不可信的。** 懒猫 SSO 继承了这个前提,你作为 App 开发者也必须继承。 --- ## 2.4 Client 真的“什么都没验证”吗? 到这里,你可能会反问一句: > “那 HTTPS 呢?我的 `alice.heiyu.space` 是懒猫官方给我签的证书, > 不是已经很安全了吗?” 这是一个非常关键的问题。 HTTPS 确实解决了很多问题,但它解决的是: * 浏览器 ↔ 懒猫 SSO 之间的**传输安全** * 容器 ↔ 懒猫 SSO 之间的**传输安全** * 防止明文被窃听 * 防止简单的中间人篡改 但 HTTPS **不解决**下面这些问题: * 返回的 token 是不是给 `xu.deploy.env` 的,还是给盒子上别的 App 的 * token 是否来自**这台盒子**的懒猫 SSO,而不是攻击者在另一台盒子上部署的懒猫 SSO * token 是否是旧的、被重放的 * 浏览器环境是否泄露了中间 code 也就是说: > **HTTPS 保护的是“通道”, > 懒猫 SSO 必须解决的是“内容”。** --- ## 2.5 为什么 Client 不能“信懒猫就好了” 另一个常见的直觉是: > “我信任懒猫这个盒子,那懒猫返回的结果我就直接信。” 这句话在逻辑上是**不完整的**。 因为它混淆了两个不同层面的“信任”: 1. **信任懒猫 SSO 这个身份源** 2. **信任某个具体结果来自这个懒猫 SSO、且适用于当前 App** 即使你 100% 信任懒猫 SSO 本身,也不能跳过第二层。 原因很简单: > **同一个懒猫 SSO 同时为盒子上所有 App 服务。** 如果 ENV 查看器不明确验证: * “这是不是发给我(`xu.deploy.env`)的?” * “这是不是 Alice 刚才那次登录的结果?” 那么一个**合法但不相关的结果**,就可能被错误使用。 比如:另一个 App(哪怕是善意的)拿到了懒猫 SSO 发给它自己的 token, 阴差阳错传给了 ENV 查看器, ENV 查看器如果不检查 `aud`,就会**用一个不属于自己的 token 完成登录**。 这不是科幻。在懒猫这种“应用彼此能互联”的架构下,`aud` 检查是**基础操作**。 --- ## 2.6 一个非常重要的转折点: ### 信任不是一个状态,而是一个过程 到目前为止,我们其实已经得到一个非常重要的结论: > **“登录成功”不是一个瞬间事件, > 而是一个逐步建立的信任过程。** 这个过程不是靠“感觉安全”,而是靠一连串明确的验证步骤。 我们可以把它抽象成一句话: > **懒猫 SSO 的核心不是“身份声明”, > 而是“身份声明的验证链”。** 懒猫只是把这条链的**起点和工具**替你准备好了(注入环境变量、生成 client_secret、暴露 discovery), 但**链上的每一环**都还是你自己负责执行的。 --- ## 2.7 验证链从哪里开始? 如果你回到第一章的流程,会发现一个关键事实: > 在 token 出现之前, > ENV 查看器已经接收过一个“关键输入”。 那就是: ``` https://env.alice.heiyu.space/callback?code=abc123&state=xyz ``` 这个 `code`,决定了 ENV 查看器能否拿到后续的一切。 于是,一个新的问题浮现出来: > **如果 code 本身被滥用, > 后面再严格的 token 校验,还有意义吗?** 这不是一个修辞问题。 这是 **PKCE** 出现的直接原因,也是懒猫 SSO 为什么在 discovery 里明确声明 `code_challenge_methods_supported: ["S256", "plain"]` 的原因。 --- ## 2.8 本章的结论(明确而不留余地) 我们可以用几句话,把这一章的核心结论说清楚: 1. ENV 查看器不能因为“流程跑通”而信任登录结果 2. 所有信任都必须来自**显式验证** 3. 验证必须覆盖流程的每一个关键输入 4. 验证链必须从 **token 出现之前** 就开始 5. “懒猫帮我注入了 CLIENT_SECRET”不是跳过验证的理由 换句话说: > **如果你只验证 token, > 那你已经晚了一步。** --- ## 2.9 接下来我们要做什么 在下一章中,我们将回到那个被忽略的问题: > **Authorization Code 暴露在浏览器里, > 为什么在懒猫场景下还敢用?** 我们会看到: * code 被拦截在家庭网络里不罕见(家里装了奇怪插件、内网多设备、公网暴露) * 纯前端 SPA 型懒猫 App 无法保密任何 secret * 懒猫虽然给你注入了 `CLIENT_SECRET`,但在某些场景下它实际上是 public 的 * PKCE 如何把一个“谁拿到都能用”的 code,变成“只能被最初那个 Client 使用” 这是 **懒猫 SSO 验证链的第一道真正闸门**。 --- ### 本章小结(一句话) > **懒猫 SSO 的安全性,不来自“我信任懒猫”, > 而来自“我验证过这次结果”。** 
评论
0暂无评论