所有文章

告别自托管的 Duende:哪些会迁移,哪些你不必再运维

AuthagonalJune 18, 2026
duendeidentityserverdotnetoidcmigration

由 AI 从英文原文翻译。 阅读原文

Duende IdentityServer 确实是一款优秀的软件:如果你想端到端地自己掌控身份层,它是 .NET 领域的标准。但"掌控"本身就是全部成本所在:你要托管它、给它打补丁、为它扩容,并自行构建它周边的一切,管理界面、MFA、审计日志、品牌定制、状态页、用户与角色管理。到了某个时刻,团队会决定宁愿不再运维一套 IdP,而唯一重要的问题就是:迁移到底有多痛苦。下面就是真正会被迁移过去的内容。

你实际在运维的是什么

自托管 Duende 让你要负责的远不止令牌端点:

  • IdP 本身:托管、扩容、打补丁以及许可证(SAML 是在其之上额外付费的附加项)。
  • 你自己构建的、围绕它的一切:管理门户、MFA 注册、审计日志、自定义品牌、状态页、用户与角色管理。

真正耗费时间的,通常就是第二份清单。

会迁移什么,而且大体上是机械化的

Duende 将其配置保存在 SQL(即 ConfigurationDb)中,将用户保存在 ASP.NET Identity 中。一次迁移会同时读取两者:

  • Clients → OAuth 客户端,包括重定向和登出 URI、CORS 来源、授权类型、refresh token 的使用/过期语义,以及 device code 的生命周期。已禁用的客户端会以禁用状态导入;已过期的 secret 会被跳过并附带一条警告。
  • Scopes → ApiScopes 和 IdentityResources 会直接映射过来。Duende 的 ApiResource 中间层(一个 audience 加上一份共享的 claims 列表)会被压平到更简单的模型上:资源名称会成为每个使用了其成员 scope 的客户端上的一个 audience,而它的 claims 会合并到这些 scope 上。
  • Users → 好消息是:ASP.NET Identity V3 的密码哈希(以及旧版的 bcrypt)会被原生验证,并在首次登录时重新哈希。无需重置密码、无需支持工单,用户毫无察觉。(这正是离开 Auth0 时真正棘手的部分,但用 Duende 就直接行得通,因为它和你的应用本就在用的是同一套哈希。)
  • 角色与分配、外部登录以及 OIDC 身份提供方都会一并迁移过来。SAML 提供方会被标记出来,供你在另一端重新配置。

关于身份稳定性的要点

和任何一次 IdP 迁移一样,必须做对的一点是不改变用户的 subclient_id:下游的 refresh token、客户 IdP 中的 SCIM 记录,以及存储在你自己数据库中的用户 ID,都会引用它们。导入器会同时保留这两者。而且,如果你的某位门户所有者同时以另一个 ID 作为 Duende 用户存在,导入器会通过分阶段、可恢复的步骤将二者协调一致(轮转为 Duende 的 sub),而不会留下一个迁移到一半、无法登录的所有者。

关于测试迁移的一点题外话(一个 .NET 陷阱)

我们针对一个真实且已填充数据的 Duende 数据库来测试导入器,而不是用 mock,结果证明这很值得。ASP.NET Identity 将 AspNetUsers.LockoutEnd 存储为 datetimeoffset,而用 reader.GetDateTime() 读取它会在该类型上抛出 InvalidCastException。因此,仅仅一个被锁定的用户就会让整个用户导入失败。只有针对真实数据(其中包含一个被锁定的用户)运行导入器,你才会发现这一点。如果你在权衡任何一款迁移工具,值得一问的就是这个问题:它是针对一个填充了数据的数据库测试的,还是只对着源 API 做了 mock?

你不再需要做什么

迁移的意义不在于迁移本身:而在于迁移之后的一切。SAML、SCIM、MFA、审计日志、自定义域名和品牌都是开箱即用的,而不再是你要构建和运维的东西,给 IdP 打补丁和扩容也不再是你的问题。如果自托管 Duende 当初是一个深思熟虑的决定("我们想要完全的控制权"),而且现在依然如此,那就留下吧,这是一个完全合理的选择。本文是为那种情形准备的:运维一套 IdP 已经不再是你愿意投入时间的地方。

如果你正在考虑

迁移大体上是机械化的,密码无需重置即可平移过来,而且预览是只读的:把它指向你的 Duende 数据库,它就会在写入任何内容之前,准确地向你展示哪些内容会被导入。

从 Duende 迁移