# 懒猫SSO第六章 WKS:`/sys/oauth/keys` 到底在干什么

忘机山人

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

在第五章,我们已经确认了一件事:

只要 ENV 查看器能拿到懒猫 SSO 的公钥,
并成功验证签名,
那它就可以确信:
这些声明只能来自懒猫 SSO。

但这句话里,隐藏着一个极其危险的前提

ENV 查看器拿到的“那把公钥”,
真的是懒猫 SSO 的公钥。

如果这一点站不住脚,
前一章所有关于签名、私钥、不可伪造的结论,都会瞬间失效。

于是,一个新的问题出现了:

ENV 查看器从哪里拿到公钥?
又凭什么相信这把公钥是对的?

这正是 JWKS 存在的原因,也是懒猫 SSO 为你暴露 https://alice.heiyu.space/sys/oauth/keys 的意义。


6.1 一个天真的、但非常危险的想法

在最早期的懒猫玩法(以及很多自建 OIDC 的教程)里,你可能见过这样一种做法:

“把懒猫 SSO 的公钥下载下来,写在应用的配置文件里。”

从功能上看,这当然可行。
但从系统设计上看,这是一个不可接受的方案

原因并不复杂。


6.2 为什么“写死公钥”在懒猫里行不通

第一:密钥一定会轮换

懒猫官方会定期做 Key Rotation

  • 懒猫 SSO 的私钥定期更换
  • 新旧密钥并存一段时间
  • 逐步淘汰旧密钥

如果 ENV 查看器把公钥写死:

  • 每次懒猫系统更新都需要同步更新
  • 一旦漏更,就会导致整个盒子的用户登录全部失败
  • 用户根本不知道发生了什么,只会觉得“这 App 又坏了”

这在工程上是不可接受的。


第二:懒猫 SSO 可能同时使用多把密钥

在真实的懒猫 SSO 实现中,签名者往往会:

  • 使用不同密钥签发不同 token
  • 在轮换过渡期内同时接受多把 key
  • 为不同算法维护不同 key(懒猫目前只有 RS256,但未来可能变)

ENV 查看器如果只“认一把”,
就会在合法场景下误判 token 为非法。


第三:公钥不是“配置”,而是“动态信任关系”

最根本的问题在于:

公钥不是一个静态参数,
而是懒猫 SSO 对外发布的一部分“信任接口”。

它应该:

  • 可发现(通过 discovery)
  • 可更新(懒猫升级后自动跟上)
  • 可验证来源(绑定到 iss

这三点,单靠配置文件是做不到的。


6.3 JWKS 是什么(准确但不绕)

JWKS(JSON Web Key Set)并不神秘。

它只是一个 JSON 格式的公钥集合
通过 HTTPS 对外发布。

懒猫 SSO 的 JWKS 地址是:

https://alice.heiyu.space/sys/oauth/keys

这个地址不是猜出来的,它由 discovery 返回的 jwks_uri 字段决定:

{
  "issuer": "https://alice.heiyu.space/sys/oauth",
  "jwks_uri": "https://alice.heiyu.space/sys/oauth/keys",
  ...
}

一个典型的返回看起来像这样:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "3e7f...",
      "use": "sig",
      "alg": "RS256",
      "n": "…",
      "e": "AQAB"
    }
  ]
}

每一项,描述的都是一把 可用于验签的公钥


6.4 JWKS 解决的不是“有没有公钥”,而是三个更深的问题

第一:发现(Discovery)

ENV 查看器不需要提前知道公钥内容。
它只需要知道:

“去哪里拿。”

这个“哪里”,不是随意指定的 URL,
而是通过 OIDC Discovery 机制获得的:

GET https://alice.heiyu.space/sys/oauth/.well-known/openid-configuration

在这里,懒猫 SSO 明确告诉 ENV 查看器:

  • 我是谁(issuer
  • 我的 token endpoint 在哪(token_endpoint
  • 我的公钥在哪jwks_uri

而这个 issuer 的值,又由你容器里的 LAZYCAT_AUTH_OIDC_ISSUER_URI 环境变量决定——
懒猫注入的这个变量,就是整个信任链的起点


第二:选择(Selection)

JWT 的 header 中,有一个非常重要的字段:

{ "alg": "RS256", "kid": "3e7f..." }

kid 的含义只有一个:

“这个 token 是用哪一把 key 签的。”

ENV 查看器在拿到的 JWKS 里:

  • 查找 kid 对应的 key
  • 用这把 key 验签

这使得:

  • 多 key 并存成为可能
  • key 轮换不需要盒子重启、也不需要 App 重部署

第三:更新(Rotation)

JWKS 是一个可随时间变化的集合

  • 懒猫升级引入新 key
  • 旧 key 保留一段时间
  • ENV 查看器通过缓存 + 刷新机制逐步更新

实现层面的建议(用主流 OIDC 库几乎都自带):

  • 缓存 JWKS 几分钟到几小时
  • 遇到没见过的 kid,主动刷新一次
  • 刷新还找不到 → 拒绝 token,不要无限循环

这让“长期运行的信任关系”成为可能——
你的 App 写好一次,懒猫这边换多少次 key 都不用动它。


6.5 Client 是如何使用 JWKS 的(流程级)

现在我们把 JWKS 放回到实际流程中。

当 ENV 查看器拿到一个 ID Token 时,它会:

  1. 解析 JWT header
  2. 读取 alg(必须是 RS256)、kid
  3. 从缓存的 JWKS 中查找对应公钥
  4. 如果找不到:
    • 重新拉取 https://alice.heiyu.space/sys/oauth/keys
    • 再次查找
  5. 如果仍然找不到:
    • 拒绝这个 token

注意这个结论:

“找不到对应公钥”
本身就是一个拒绝信号。

如果你的 App 在这里写了 fallback(“找不到就跳过签名验证”),
那整个懒猫 SSO 的安全模型在你这里就破了。


6.6 为什么“中间人伪造 JWKS”行不通

这是一个经常被提出、也非常值得认真回答的问题:

“如果有人在家庭网络里拦截到 /sys/oauth/keys 的请求,
返回一套伪造的公钥,会怎样?”

这个攻击模型并不成立,原因不在于 JWKS 本身,而在于:

JWKS 从来不是“裸奔”的。


6.6.1 HTTPS 是第一道信任根

JWKS 是通过 HTTPS 获取的,而且 heiyu.space 的证书是懒猫官方签发的可信证书。

这意味着:

  • ENV 查看器会验证 TLS 证书
  • 会验证域名
  • 会验证证书链

要成功伪造 JWKS,中间人必须:

  • 控制 DNS
  • 拥有可信 CA 签发的 *.heiyu.space 证书

这已经超出了懒猫 SSO 的威胁模型。

⚠️ 例外:如果你手贱在 App 里关掉了 TLS 验证(比如用自签证书做开发调试),这条就不成立了。别在生产配置里关 TLS 验证。


6.6.2 issuer 绑定是第二道闸门

ENV 查看器并不是“随便拉一个 JWKS”。

它的逻辑是:

“这个 JWKS 属于 https://alice.heiyu.space/sys/oauth 这个 issuer。”

而 issuer 本身,已经在 discovery 阶段被固定(来自 LAZYCAT_AUTH_OIDC_ISSUER_URI)。

攻击者即使伪造一套 JWKS,也无法绕过:

  • iss 校验
  • aud 校验
  • discovery 绑定

image.png

6.6.3 公钥可以公开,但私钥无法伪造

这是整个模型中最容易被忽略的一点:

安全性不来自“公钥保密”,
而来自“私钥不可得”。

攻击者可以:

  • 复制懒猫 SSO 的公钥
  • 重发它的 JWKS
  • 声称“这是我的 key”

但只要他没有懒猫 SSO 的私钥:

就无法生成任何能通过验签的 token。


6.7 一个非常重要的实现原则(必须强调)

ENV 查看器在实现 JWKS 支持时,必须遵守一个原则:

永远不要从 token 本身“学会”信任来源。

具体来说:

  • ❌ 不要信任 token header 里的 jku
  • ❌ 不要信任 token header 里的 x5u
  • ❌ 不要允许 token 指定“去哪拉公钥”
  • ❌ 不要把 jwks_uri 做成“App 用户可配置的”(这是一个经典反面教材)

JWKS 的来源,必须是:

懒猫注入的 LAZYCAT_AUTH_OIDC_ISSUER_URI → discovery → jwks_uri

这是防止 Key Injection 的关键。


6.8 到这里,我们真正拥有了什么

在本章结束时,ENV 查看器已经具备了三项能力:

  1. 能验证 token 的签名
  2. 能确信使用的是懒猫 SSO 发布的公钥
  3. 能适应密钥轮换而不中断服务

这意味着:

ENV 查看器已经能确认:
“这组声明确实来自这台盒子的懒猫 SSO,且没有被篡改。”

但请注意:

我们仍然没有回答一个更关键的问题。


6.9 合法的声明,仍然可能是错误的声明

一个 token 可以:

  • 签名完全正确
  • 公钥完全可信

但仍然可能:

  • 发给盒子上别的 App(audsome.other.app
  • 用在错误的场景
  • 来自错误的 issuer(你接入了多个 OIDC,比如 Casdoor)
  • 已经过期
  • 被重放

也就是说:

“来源可信”

“现在就该信任并使用这些声明”。


6.10 本章结论(必须清晰)

我们可以把这一章的结论总结为:

  1. 懒猫 SSO 的公钥通过 https://<盒子>.heiyu.space/sys/oauth/keys 发布
  2. JWKS 解决的是“公钥发现、选择与轮换”问题
  3. ENV 查看器对公钥的信任,来自 HTTPS + LAZYCAT_AUTH_OIDC_ISSUER_URI 绑定
  4. 公钥可以公开,懒猫 SSO 的私钥不可伪造
  5. 签名验证通过,只是信任链的一部分

image.png

本章小结(一句话)

JWKS 让“数学可信”变成了“懒猫 App 可用的可信”。

评论

0

暂无评论

说点什么呢~
收藏
0
0
0