懒猫SSO第一章: 一次“看起来很普通”的懒猫登录

忘机山人

发布于61天前
博客图片修整中,看不了可以先搜索公众号“忘机山人”看。

在懒猫微服这种家庭服务器系统里,登录是一个被严重低估的动作。
因为它看起来实在太简单了。

https://appstore.lazycat.cloud/#/shop/detail/xu.deploy.env

你打开 https://env.alice.heiyu.space/,看到一个“使用懒猫账号登录”的按钮,点一下,浏览器跳到一个写着 Grant Access 的页面,你点同意,页面又跳回应用,头像出现在右上角。

几秒钟之内,一切完成。

如果我们只从“用户体验”的角度看,这个过程几乎没有任何值得讨论的地方。
懒猫把它做得越无感越好。

但如果你从安全和系统设计的角度看,这个过程恰恰是整个盒子里风险最高、假设最多、也最容易被误解的部分

在这一章里,我们什么都不“深挖”。
我们只做一件事:

完整走一遍一次标准的懒猫 SSO 登录流程,
然后在每一个“看起来理所当然”的地方,埋下问题。

这些问题,会在后面的章节里逐一被拆解。


1.1 一个具体而普通的场景

我们先固定一个非常具体的例子,后面所有章节都会围绕它展开。

  • 用户:alice,她是这台盒子的主人,邮箱 alice@example.com
  • 盒子名:alice,对外域名是 alice.heiyu.space
  • Client:懒猫 ENV 查看器,包名是 xu.deploy.env
    • manifest 里设置了 subdomain: envoidc_redirect_path: /callback
    • 所以它自己的外部地址是 https://env.alice.heiyu.space/
  • 登录方式:Authorization Code Flow + PKCE(懒猫 SSO 只支持这一种 response_type)
  • IdP:懒猫 SSO(盒子自带的身份服务)
    • issuer:https://alice.heiyu.space/sys/oauth

Alice 在浏览器里打开 ENV 查看器,看到一个熟悉的按钮:

“使用懒猫账号登录”

她点击了这个按钮。


1.2 第一步:浏览器被重定向

点击按钮后,ENV 查看器的后端并没有自己处理登录。

它做的第一件事是:

把浏览器重定向到一个“外部的地方”

具体来说,是跳到懒猫 SSO 的授权端点:

GET https://alice.heiyu.space/sys/oauth/auth?...

注意:域名前缀是 alice.heiyu.space而不是 env.alice.heiyu.space
也就是说,浏览器离开了应用自己的子域,跳到了盒子的主域

从浏览器视角看,只发生了一件事:

GET /authorize-like URL → 跳转到懒猫 SSO

页面变了,子域变了,UI 也变成了懒猫那套统一的 Grant Access。

到这里,一切都非常合理。

ENV 查看器并不保存用户密码,也不直接验证身份。
它把这件事外包给盒子里的懒猫 SSO。

这也是懒猫 SSO 存在的意义:

盒子里有一个角色专门负责“我是谁”,
所有 App 都从它那里获取身份,而不是各自重新实现一遍登录。


1.3 第二步:用户在懒猫 SSO 完成登录

Alice 可能早就在懒猫官方客户端登录过,session 还在,
那这一步她只会看到一个很短的 Grant Access 确认页,点一下同意就走。

也可能 session 过期了,她需要重新输入密码、或者走 2FA。

这些细节对 ENV 查看器来说是不可见的

从 ENV 查看器的角度看,懒猫 SSO 做了三件事:

  1. 验证用户身份

  2. 询问用户是否同意把部分身份信息返回给 Client(基于 scope

  3. 准备一个“结果”,用于告诉 Client:

    “这个用户已经通过验证了”

关键在于:
ENV 查看器并没有参与其中任何一步。

它只能等待结果。


1.4 第三步:浏览器“带着结果”返回 Client

登录完成后,懒猫 SSO 并不会直接把结果“发”给 ENV 查看器。

它做的仍然是一个浏览器跳转:

302 Redirect → https://env.alice.heiyu.space/callback?code=abc123&state=xyz

浏览器地址栏里,多了两个参数:

  • code=abc123
  • state=xyz

这个 code,就是所谓的 Authorization Code

从流程上看,这是一个非常自然的设计:

  • 懒猫 SSO 不直接把最终凭证交给浏览器
  • 只给一个短期、一次性的“中间凭证”
  • ENV 查看器再用这个凭证去后台换取真正的结果懒猫 SSO 对 authorization code 的有效期卡得很短(典型是几十秒),而且一次性——用过一次就作废。

到这里为止,大多数介绍 OIDC 的文章,都会轻描淡写地说一句:

“然后 Client 用 code 换取 token”

但我们先不要急着往下走。

我们先停在这里,问一个非常基础、但经常被忽略的问题


1.5 第一个被忽略的问题:

这个 code 是不是“安全的”?

此刻,这个 code

  • 出现在浏览器 URL 中
  • 经过了浏览器
  • 经过了家庭网络
  • 可能被 nginx / openresty 的日志记录(懒猫里反代很常见)
  • 可能被浏览器扩展读到
  • 可能在 Referer 头里泄漏给其他请求

但在流程描述中,我们默认了一个前提:

只有 ENV 查看器的后端能用这个 code。

问题是:
这个前提成立吗?

在这一章里,我们不回答这个问题。
但请你记住它。

因为这正是 PKCE 出现的原因——懒猫 SSO 把它写进了 code_challenge_methods_supported: ["S256", "plain"],不是装饰。


1.6 第四步:Client 用 code 换取 token

ENV 查看器的后端(运行在盒子里的一个容器内)向懒猫 SSO 发起请求:

POST https://alice.heiyu.space/sys/oauth/token
  grant_type=authorization_code
  code=abc123
  redirect_uri=https://env.alice.heiyu.space/callback
  code_verifier=...
  + client_secret_basic 认证(从 LAZYCAT_AUTH_OIDC_CLIENT_ID / _SECRET 读)

注意这一步有一个非常重要的特点:

它不经过浏览器,是容器到容器的内部 HTTPS 请求。

懒猫 SSO 校验这个 code,然后返回一组 token:

  • access_token
  • id_token
  • refresh_token(需要请求了 offline_access

这一步,标志着真正的身份材料已经被发出。

也是从这里开始,大多数应用会认为:

“登录已经完成了。”

但如果你站在 ENV 查看器的立场,这个结论其实来得太早。


1.7 一个看起来“理所当然”的假设

现在,ENV 查看器手里拿到了一个 id_token

它是一个字符串,看起来像这样:

eyJhbGciOiJSUzI1NiIsImtpZCI6IjE2In0.
eyJpc3MiOiJodHRwczovL2FsaWNlLmhlaXl1LnNwYWNlL3N5cy9vYXV0aCIsInN1YiI6ImFsaWNlIn0.
XK9k3...

文档告诉你:

  • 这是一个 JWT
  • 里面包含用户身份信息(subemailpreferred_usernamegroups……)
  • 是懒猫 SSO 签发的,算法是 RS256

很多系统在这里会直接做一件事:

解析 token → 取出 emailpreferred_username → 创建 session

这一步在功能上是可行的。
盒子里 99% 的情况下也确实不会出事。
但在安全上,它隐藏了一整条未经验证的假设链

我们把这些假设逐条写出来:

  1. 这个 token 真的是这个盒子的身份服务 发的
  2. 中途没有被人修改
  3. 是发给 xu.deploy.env 这个 Client 用的
  4. 没有过期
  5. 没有被重放
  6. 返回结果对应的是 Alice 刚才那次登录,而不是她上一次登录残留的 token
  7. preferred_username 没有被伪造成 admin

如果你发现:

流程中并没有任何一步“自动保证”这些假设成立

那你已经走在正确的方向上了。

家庭服务器的环境没有你想象的那么“封闭”——
浏览器、内网其他设备、被邀请来的家人的手机,都不是纯净的执行环境。


1.8 本章真正要问的问题

到目前为止,我们只是完整走了一遍 Happy Path。

没有攻击者、没有恶意插件、没有家里误装的奇怪扩展、没有被劫持的 WiFi。

但即便在这种“理想情况”下,ENV 查看器也只是被动地接收结果

于是,本章最后我们只留下一个问题:

ENV 查看器凭什么相信:
这个 id_token 里写的 “alice”,
就真的是刚刚那个在 Grant Access 页面点同意的 alice?

这个问题,不能靠“协议说可以”。
也不能靠“懒猫帮我注入了 CLIENT_SECRET”。
更不能靠“反正是我家,没人攻击我”。

它只能靠一套明确、可验证、可失败的逻辑链来回答。


1.9 接下来会发生什么

在接下来的章节中,我们会做三件事:

  1. 在 token 出现之前,解决“code 会不会被偷用”的问题
    —— 这就是 PKCE,也是懒猫 SSO 在 discovery 里明确支持 S256 的原因

  2. 在 token 出现之后,解决“token 是不是可信”的问题
    —— JWT、RS256、公钥、JWKS(/sys/oauth/keys

  3. 在使用 token 之前,解决“我该不该信这个声明”的问题
    —— issaudexpnoncegroups 等校验懒猫 SSO 的安全性,不在某一个点上。
    而在于:这些问题一个都没有被跳过。


本章小结(不讲技术,只讲直觉)

登录看起来像一个动作,
但在盒子里,它其实是一条验证链的起点。

在你理解这条验证链之前,
“登录成功”这四个字,本身是不完整的。

image.png

评论

0

暂无评论

说点什么呢~
收藏
0
0
0