[{"data":1,"prerenderedAt":322},["ShallowReactive",2],{"work-\u002Fworks\u002Fauth-one":3,"work-surround-\u002Fworks\u002Fauth-one":313},{"id":4,"title":5,"body":6,"cover":292,"date":293,"demo":294,"description":295,"draft":296,"extension":297,"featured":298,"meta":299,"navigation":298,"path":300,"repo":294,"seo":301,"stack":302,"stem":311,"updated":294,"__hash__":312},"works\u002Fworks\u002Fauth-one.md","Nexus ID - 多租户身份与 OIDC 平台",{"type":7,"value":8,"toc":281},"minimark",[9,17,20,27,31,61,64,67,70,81,114,117,121,128,131,137,157,164,168,175,185,207,214,220,231,235,242,245,248,255,258,272,275,278],[10,11,12,16],"p",{},[13,14,15],"code",{},"Nexus ID"," 是一套面向企业应用的统一身份中心。它不只负责“用户名加密码换 JWT”，还把租户、控制台操作员、终端用户、应用授权、会话撤销、MFA、OIDC 和审计放进同一个安全边界里。",[10,18,19],{},"这个项目最重要的决定，是把安全上下文当成业务主线：一次请求先确定“谁在访问、代表哪个租户、以什么角色访问”，再允许它读取数据或改变状态。协议能力只是入口，租户边界、令牌生命周期和可追溯性才是身份平台能否长期运行的基础。",[10,21,22],{},[23,24],"img",{"alt":25,"src":26},"Nexus ID 统一身份中心总体架构","\u002Fimages\u002Fworks\u002Fauth-one\u002Fsystem-overview.svg",[28,29,30],"h2",{"id":30},"先分开控制面与终端身份",[10,32,33,34,37,38,41,42,45,46,49,50,49,53,56,57,60],{},"Nexus ID 现在明确区分两类账户：",[13,35,36],{},"operator_accounts"," 是登录 SaaS 控制台的人，",[13,39,40],{},"users"," 是租户内的终端身份。控制台账户通过 ",[13,43,44],{},"tenant_members"," 加入租户，成员角色为 ",[13,47,48],{},"owner","、",[13,51,52],{},"admin",[13,54,55],{},"developer"," 和 ",[13,58,59],{},"viewer","；终端用户则拥有自己的应用授权、部门、职位、第三方账号和会话。",[10,62,63],{},"这条边界解决了早期统一用户表最容易出现的混乱：管理员不再因为拥有某个终端用户角色，就自动获得所有租户的超级权限；租户成员也不等同于业务应用中的登录用户。邀请 token 只保存 SHA-256 摘要，接受邀请和切换租户都会重新校验当前成员关系。",[10,65,66],{},"控制台负责租户、成员、应用、OIDC Client、Webhook、邮件模板和运营设置；终端用户入口负责注册、登录、个人资料、应用授权、第三方绑定、会话和安全设置。两条链路共用底层服务，但不共用身份、Cookie 或会话语义。",[28,68,69],{"id":69},"一次请求如何获得安全上下文",[10,71,72,73,76,77,80],{},"所有 ",[13,74,75],{},"\u002Fapi\u002Fv1"," 请求先经过 Gin 中间件链：request id、OpenTelemetry、HTTP 指标、结构化日志、恢复、语言、CORS、传输加密、限流和统一错误信封。认证中间件验证 token 或控制面 session 后，才会把当前用户、operator、tenant 和 member role 写入 ",[13,78,79],{},"TenantContext","。",[10,82,83,84,87,88,91,92,95,96,99,100,56,103,106,107,49,110,113],{},"tenant-scoped repository 不直接从连接池拿一个裸 ",[13,85,86],{},"Queries"," 执行 SQL，而是通过 ",[13,89,90],{},"TenantExecutor"," 开启短事务，并执行 transaction-local 的 ",[13,93,94],{},"set_config('app.tenant_id', ...)","。查询仍然显式携带 ",[13,97,98],{},"tenant_id","，PostgreSQL 的 ",[13,101,102],{},"FORCE ROW LEVEL SECURITY",[13,104,105],{},"WITH CHECK"," 则作为遗漏过滤时的最终防线。运行角色没有 ",[13,108,109],{},"SUPERUSER",[13,111,112],{},"BYPASSRLS"," 或租户表 owner 权限；迁移、回填和系统级维护使用独立的受控 capability。",[10,115,116],{},"这是一种纵深防御，而不是把 RLS 当成一句配置口号。应用层的查询意图可审查，数据库层可以阻断一次漏写条件，A\u002FB 租户 fixture 则验证读取、更新、删除、成员邀请和 OIDC 数据都不能跨边界。",[28,118,120],{"id":119},"登录之后才开始真正的身份生命周期","登录之后，才开始真正的身份生命周期",[10,122,123,124,127],{},"登录入口不止密码：当前服务同时提供第三方 OAuth、Passkey、扫码登录和原生客户端邮箱验证码。密码、OAuth 或 Passkey 通过后，如果账号或租户启用了 MFA，服务端不会直接签发会话，只会在 Redis 中放入五分钟、一次性消费的 ",[13,125,126],{},"mfa_ticket","。用户必须完成 TOTP 或一次性恢复码挑战，失败会作废票据并记录安全事件。",[10,129,130],{},"TOTP 按 RFC 6238 校验，并记录已接受的时间步，阻止同一窗口内的验证码重放；恢复码只存哈希且只能消费一次。控制台操作员有独立的 MFA 票据和 15 分钟 step-up 窗口，敏感的租户、成员、密钥和授权变更不能只凭一个长时间未验证的登录状态。",[10,132,133],{},[23,134],{"alt":135,"src":136},"Nexus ID 登录、MFA 与会话生命周期","\u002Fimages\u002Fworks\u002Fauth-one\u002Fauth-session-flow.svg",[10,138,139,140,143,144,49,147,56,149,152,153,156],{},"会话的事实来源是 PostgreSQL ",[13,141,142],{},"sessions","，Redis 只缓存活动或撤销状态。终端用户 access token 使用每租户的 RS256 密钥签名，默认 TTL 为数分钟，claims 带 ",[13,145,146],{},"kid",[13,148,98],{},[13,150,151],{},"session_id","，资源服务器可以从 JWKS 离线验证。refresh token 是不透明值，数据库只保存 SHA-256 哈希，每次刷新都会轮换并记录 ",[13,154,155],{},"family\u002Fparent"," 链；旧 token 再次出现会吊销整个 family 和所属会话，而不是简单返回一次错误。",[10,158,159,160,163],{},"浏览器模式下 refresh token 通过 ",[13,161,162],{},"HttpOnly + Secure + SameSite"," Cookie 交付，刷新请求还必须通过 Origin\u002FReferer 与双提交 CSRF 校验；access token 只留在内存。登出、改密、重置密码、禁用用户和暂停租户都会撤销关联会话，Redis 不可用时高权限会话校验按 fail closed 处理。",[28,165,167],{"id":166},"把统一登录变成标准-oidc","把统一登录变成标准 OIDC",[10,169,170,171,174],{},"Nexus ID 使用 ",[13,172,173],{},"zitadel\u002Foidc"," 的协议实现，按租户提供独立 issuer：",[176,177,183],"pre",{"className":178,"code":180,"language":181,"meta":182},[179],"language-text","{issuer_base_url}\u002Foidc\u002F{tenant-slug}\n","text","",[13,184,180],{"__ignoreMap":182},[10,186,187,188,49,191,49,194,49,197,49,200,56,203,206],{},"每个 issuer 暴露 discovery、JWKS、",[13,189,190],{},"\u002Fauthorize",[13,192,193],{},"\u002Foauth\u002Ftoken",[13,195,196],{},"\u002Fuserinfo",[13,198,199],{},"\u002Foauth\u002Fintrospect",[13,201,202],{},"\u002Frevoke",[13,204,205],{},"\u002Fend_session","。public client 强制 Authorization Code + PKCE S256；confidential client 的 secret 只在创建或轮换时显示一次，数据库只保存哈希和状态。redirect URI 精确匹配，不允许通配符；授权码只存哈希，绑定 client、redirect、PKCE、nonce 和 session，TTL 两分钟且原子消费。",[10,208,209,210,213],{},"Hosted Login 通过 opaque 的 authorization request id 与业务前端衔接：用户在 Nexus ID 页面完成密码、第三方登录、MFA 和 consent 后，服务端确认用户与 pending request 属于同一租户，再让协议库回跳到已登记的 callback。scope 扩大需要重新 consent，拒绝也会带着原始 ",[13,211,212],{},"state"," 返回 RP。OIDC refresh grant 复用第一方会话与 refresh rotation，不维护第二套令牌家族。",[10,215,216],{},[23,217],{"alt":218,"src":219},"Nexus ID 多租户 OIDC 授权与数据隔离","\u002Fimages\u002Fworks\u002Fauth-one\u002Ftenant-oidc-flow.svg",[10,221,222,223,226,227,230],{},"OIDC access token 的 ",[13,224,225],{},"jti"," 会在 Redis 中登记，introspection 必须同时通过 JWT 校验并命中登记，撤销后可以立即返回 ",[13,228,229],{},"active: false","；只做离线验证的资源服务器则依靠 60 到 900 秒的短 TTL 收敛。",[28,232,234],{"id":233},"应用授权与外部-api","应用授权与外部 API",[10,236,237,238,241],{},"除了 OIDC Client，平台还保留面向内部系统的 API Client 管理：一个 client 可以绑定允许访问的内部应用，再通过 ",[13,239,240],{},"method + path_pattern + scope"," 授予接口级权限，并附加 IP、用户过滤条件和限速。请求进入外部 API 后先校验凭证，再检查应用范围和接口权限，所有结果进入审计日志。",[10,243,244],{},"兼容的 HMAC-SHA256 请求会把 method、path、timestamp、nonce 和 body 组成 canonical request，Redis 负责一次性 nonce，可信代理和 IP allowlist 共同限制来源。API Client 绑定可访问的内部应用、接口权限和 scope，鉴权结果统一进入审计日志。",[28,246,247],{"id":247},"审计不是请求日志的副产品",[10,249,250,251,254],{},"Nexus ID 将两种记录分开处理：结构化 HTTP 日志用于排查请求，",[13,252,253],{},"security_events"," 用于回答“谁在什么时候以哪个 tenant、session、client 做了什么”。登录成功\u002F失败、MFA challenge、refresh 重放、会话撤销、OIDC 授权批准\u002F拒绝、consent 和密钥操作都进入安全事件。",[10,256,257],{},"日志入库前会递归脱敏密码、token、验证码和 secret；租户内审计序列维护 hash chain，数据库 trigger 将日志表设为 append-only，管理员可以通过链校验接口发现删除或插入。控制面写操作先写 attempt 审计，审计不可用时返回 503，而不是悄悄完成一次无法追溯的权限变更。",[10,259,260,261,49,264,267,268,271],{},"邮件验证码、邀请、登录告警和 Webhook 由后台 worker 投递，失败任务可重试。Prometheus 和 OpenTelemetry 覆盖 HTTP、认证、限流、数据库和 Redis 健康；",[13,262,263],{},"\u002Flivez",[13,265,266],{},"\u002Freadyz"," 与 ",[13,269,270],{},"\u002Fmetrics"," 用于容器和 systemd 的运行检查。敏感配置可在启动前由 OpenBao Transit 解密，Docker Compose 提供 PostgreSQL、Redis、后端和 Nuxt 前端的本地编排。",[28,273,274],{"id":274},"实现与验证",[10,276,277],{},"仓库通过自动化测试覆盖多租户 TenantExecutor 与 RLS、RS256\u002FJWKS、会话和 refresh rotation、TOTP 与恢复码、控制面 MFA、OIDC Authorization Code + PKCE、审计 hash chain、Redis 限流和 CI 门禁。项目同时提供 OpenAPI、SPA PKCE 示例、confidential web 示例、迁移工具、seed、租户回填和 Linux 部署 runbook。",[10,279,280],{},"这个项目想解决的不是“再做一个登录页”，而是把身份系统中的关键问题放进主链路：租户如何隔离，管理员与终端用户如何分开，token 如何撤销，MFA 如何防重放，OIDC 如何遵守标准，授权变化如何留下证据。Nexus ID 将这些能力组合成一套统一身份平台，为控制台、终端用户和业务应用提供一致的认证与授权基础。",{"title":182,"searchDepth":282,"depth":282,"links":283},3,[284,286,287,288,289,290,291],{"id":30,"depth":285,"text":30},2,{"id":69,"depth":285,"text":69},{"id":119,"depth":285,"text":120},{"id":166,"depth":285,"text":167},{"id":233,"depth":285,"text":234},{"id":247,"depth":285,"text":247},{"id":274,"depth":285,"text":274},"\u002Fimages\u002Fworks\u002Fauth-one\u002Fproject-cover.svg","2026-03-10",null,"基于 Go、PostgreSQL 与 Redis 构建的统一身份中心，覆盖多租户隔离、可撤销会话、MFA、WebAuthn、OIDC 授权服务器、应用授权与安全审计。",false,"md",true,{},"\u002Fworks\u002Fauth-one",{"title":5,"description":295},[303,304,305,306,307,308,309,310],"Go","Gin","PostgreSQL","Redis","Nuxt 4","OIDC","WebAuthn","OpenTelemetry","works\u002Fauth-one","9t9x2OcalZRMPh2hjQ0AW7GMTbFA4Tas4nfM7i91h0Y",[314,318],{"title":315,"path":316,"stem":317,"children":-1},"海燕边缘网关 haiyan-io","\u002Fworks\u002Fhaiyan-io-work","works\u002Fhaiyan-io-work",{"title":319,"path":320,"stem":321,"children":-1},"OmniCore - 企业业务能力中心","\u002Fworks\u002Fomnicore","works\u002Fomnicore",1787046421621]