派生系统的快速集成:从控制中心到认证基座的设计演进


摘要

本文探讨了企业内部多系统集成的发展历程与架构演进。从最初的信息孤岛现象出发,分析了SaaS模式的优势与局限性,进而引出私有化部署的必然需求。基于作者两年来的实践与思考,记录了从”中央控制系统”设想到”认证基座”理念转变的心路历程,最终借鉴OAuth 2.0框架和声明式服务发现思想,设计了一套支持子系统离线自治的中央认证系统方案。

关键词:系统集成;单点登录;JWT;服务发现;离线自治


1. 问题背景:企业信息化的困境

1.1 信息孤岛的必然

很多时候,企业内部开发了很多子应用。但是由于各个部门之间的沟通成本问题,这些子系统往往成为一个个信息孤岛——每个系统都设计了各自的权限体系,各自维护一套用户数据。

对于企业来说,这是难以治理的。不过,大型企业往往会自发产生治理的动力,毕竟从内部成本控制的角度来看,统一治理是值得做的事情。

1.2 软件服务模式的演变

在前些年,很多IT公司以提供咨询服务为主要业务,通过技术或人力外包的方式,为那些不具备开发条件的企业提供技术服务。

这类以技术咨询和支持为主的公司,通常会开发各式各样的小型系统。有的公司会将其中一些系统留作样本,遇到相似业务需求时,调整些许代码,以一个新信息系统的形式售卖出去。这些应用还会被放在官网门户中作为模板宣传,以彰显公司的实力和丰富经验。

在这些企业中,有的会逐步发展成大型外包公司。公司内部培养的产品经理是宝贵人才,同时也培养了一大批技术架构方面的储备人才。此时,内部会自发产生一种趋势:将已有的应用抽象为可重复利用的小型公共组件或公共逻辑。这些小型组件或通用的运行逻辑,逐渐成为公司的核心能力,最终以服务的形式对外售卖这些抽象而复杂的功能逻辑。

后来,人们将这种模式称为SaaS系统。

2. SaaS的局限与私有化诉求

2.1 SaaS的天然缺陷

SaaS有一个很重要的特点:很多功能必须依赖其平台,离开了平台软件就无法继续运行。这正是SaaS公司得以持续经营的关键——客户愿意依赖SaaS,是因为成本更低廉,还附带持续维护服务。

然而,SaaS不可能覆盖所有企业的特殊定制需求。有些企业并不认可那些经过实践锤炼的通用功能,而是希望用自己的逻辑来执行——这是客观存在的合理诉求。因为SaaS本就是为通用场景设计的,面对特定场景的特殊逻辑,自然难以满足需求。这些问题或许会在SaaS后续迭代中解决,但SaaS的发展速度不可能总是领先于业务需求。

2.2 数据主权的考量

还有一个关键问题:SaaS提供了完整的数据存储和处理链路,但数据都掌握在SaaS厂商手中。当企业不希望某些数据落在SaaS厂商手里时,他们就不会选择SaaS方案。

2.3 私有化部署的矛盾

以上问题和需求,催生了私有化部署的诉求。然而,将完整的SaaS功能直接私有化部署是不合时宜的——SaaS中的很多功能企业根本用不上,部署在企业内部会造成显而易见的资源浪费。

3. 我的思考演进

3.1 初期的设想:中央控制系统

两年前,我在中联信通任职Web前端时,做的就是承包信息系统的业务。当时和同事在工作之余讨论过一个概念——中央系统。

中央系统的原始构想:作为信息系统开发的承包商,我们希望内部有一个系统,用于接管所有子系统。但这些子系统必须能够随时脱离中央系统独立运行。

我之所以有这样的想法,是因为公司每次承包新系统时,都是基于源码克隆一个独立项目。之前积累的公共逻辑、已经建设好的字典、功能模块(如邮件模块、日志模块、文件存储模块、系统配置模块),大部分都用不上,或者需要重新配置。这些模块在前端和后端都是源码级别的,每次克隆都要单独配置一次。

我当时的设想:如果存在这样一个中央系统,她负责接管子系统的配置,例如提供”一键复制子系统”的能力。作为开发人员,我只需要在中央系统中配置一个新应用,为它设定菜单,然后为每个菜单选定之前承包项目做过的某些业务页面——这样就能实现不用修改代码,快速生成一个完全不同的自由组合的应用,而不是像现在这样每次都克隆完整的仓库。

脱机功能的考虑:考虑到开发的系统还需要能够脱离中央系统独立交付,我后来补充了”系统脱机”的功能设想——提供一个功能界面,可以将某个子系统的所有相关数据库表(包括菜单数据、字典、系统配置等)导出为一个完整的SQL文件。

3.2 对初期设想的反思

直到最近两天,我开始着手实现具体的数据库设计时,参考了AI给出的建议。果不其然,AI给出了社区中更好的实现方式。

原方案的局限性

我的设计仍然需要人工参与部署——需要导出SQL、需要独立部署,因为子系统最终要脱离中央系统并售卖给合作企业。这种设计无形中增加了不少部署难度。

更重要的是,采用中央系统控制子系统、并为子系统下发配置的模式,对子应用的限制太多。例如,子系统必须与中央系统建立连接;一旦网络中断或中央系统不可访问,子系统就无法拉取配置和运行时信息,基本无法运行。这对于工业场景”停网不停机”的特殊要求来说,是不可接受的。

3.3 理念的转变:从控制到认证

结合最近阅读的《API安全进阶:基于OAuth 2.0框架》(第二版,普拉巴斯·西利瓦德纳),我对原有的假设产生了新的看法。

原先计划的仅仅是单纯的账号密码统一——所有平台共用同一套账户密码。但对终端用户来说,这并不算真正的便利。真正的便利应该是单点登录(SSO)的体验。

重新定位中央系统的角色:中央系统应该是提供认证和应用基座的角色,而不是控制一切的”中央集权”。

3.4 两种主流模式的对比

模式职责范围优点缺点
完全托管中央系统控制所有角色与菜单绑定子系统所有状态和配置信息,中央系统均掌握子系统不够独立,扩展权限逻辑非常麻烦
底座/门户型中央系统仅管理”谁有权进入哪个子系统”子系统高度自治,子系统A可用传统开发,子系统B可采用低代码集成深度较浅

综合来看,考虑到子系统需要能够脱离中央系统独立运行,底座型显然更适合项目承包的业务场景。

此时,中央系统的定位应该是以应用和用户为中心,子系统只需要对接认证中心。脱离中央系统后,子系统可以使用独立的登录逻辑,实现轻量化部署。

这里有一个需要商榷的问题:子系统脱离后,未来可能还需要重新对接回来——即重新连接中央系统。这要求设计上具备良好的”热插拔”能力。

4. 权限与用户的抉择

4.1 中央系统不该管什么

由上述中央系统角色的演变逻辑来看,中央系统不需要控制子系统的权限逻辑。

如果中央系统管理权限逻辑,将导致权限系统可扩展性变差、甚至僵化。不同业务场景下的权限模型差异很大——有的子系统适合RBAC(基于角色的访问控制),有的可能需要ABAC(基于属性的访问控制)。中央系统无法预知所有可能性。

4.2 中央系统应该管什么

因此,中央系统最好的功能定位就是认证和授权——实现账户的认证和授权能力。

在现代架构中,中央角色的定位就是提供**单点登录(SSO)功能,成为访问所有子系统的授权门户。具体实现上,可以采用OAuth 2.0框架中的身份联合(Identity Federation)**模式。

最终,中央系统只负责两件事:

  1. 管理账号
  2. 下放令牌(JWT)

不负责任何子系统的具体业务逻辑和权限细节。

4.3 JWT的角色传递

在JWT的设计上,我们可以在令牌中携带用户在指定应用下的角色信息:

{
"sub": "user_123456",
"name": "张三",
"exp": 1893456000,
"apps": {
"order_system": ["admin", "operator"],
"crm_system": ["viewer"]
}
}

子系统拿到JWT后,可以验证签名、解析用户信息,并根据令牌中的角色信息,结合本地菜单配置,动态决定展示内容。

核心思想:子系统自备菜单,并在菜单配置中标注required_role: "admin"。解析JWT后,如果发现用户角色是”guest”,则在本地过滤掉需要”admin”角色的菜单。

这样,子系统就能实现完全不依赖中央系统的「菜单+权限」闭环。

4.4 社区同类实现

实际上,社区中已经有类似的中央系统实现,如NacosApolloKeycloak等。不过,大部分社区实现采用的是中央系统控制子系统大部分信息的方式——典型的多租户模式,一套代码给上千人使用,配合配置中心负责配置推送和SDK注入。中央系统实现RBAC/ABAC全栈托管,核心目的是配置中心化。

而我的设计思路与上述实现有所不同:更强调子系统的独立性和离线自治能力。

5. 最终架构设计

5.1 核心原则:角色反转

综合以上思考,为了实现快速0-1搭建与快速交付,同时满足子系统断开中央系统后仍能正常运行的弱依赖设计目标,我确定了以下设计原则:

角色反转——从”中央控制”转向”子系统自治+中央观察”:

  • 子系统侧(Owner):菜单定义在子系统的代码或本地配置中。子系统最清楚自己有哪些功能。离线时,直接读取本地定义的菜单,完美运行。
  • 中央系统侧(Observer/Aggregator):作为一个”展示窗口”。当子系统在线时,主动将菜单结构推送(或由中央系统拉取)给中央系统。

这样做的好处显而易见:

  • 彻底解耦:子系统不再是被动接受指令的”木偶”,而是具备独立生命周期的”模块”
  • 配置一致性:永远不会出现”中央系统配了菜单,但子系统代码没实现”的尴尬

5.2 配置同步机制

在决定让子系统自备菜单并上报、同时要求离线自治后,中央系统的定位确实发生了根本性变化——从一个”独裁的控制中心”转变为一个”服务发现与资源协同中心”。

具体机制

  1. 主动上报:子系统在启动时、或配置变更时,主动向中央系统上报自己的菜单结构、字典数据
  2. 版本控制:上报API支持版本参数,即使API发生变化,原有接口也不会失效
  3. 被动接收:中央系统负责接收并存储上报数据,提供统一展示窗口,但不强制下发
  4. 快照机制:子系统在本地维护配置快照,离线时直接读取快照

5.3 离线验证流程

在离线或弱依赖场景下,采用JWT + 非对称加密是最主流的方案:

  1. 初始化阶段

    • 中央系统生成密钥对(私钥private.key,公钥public.key)
    • 将公钥以配置文件或环境变量的形式,预先分发并部署在每个子系统中
  2. 登录阶段

    • 用户在中央门户登录,中央系统生成JWT
    • JWT载荷中包含用户信息、过期时间、在各应用下的角色列表
    • 中央系统用私钥对JWT签名
  3. 跳转阶段

    • 用户携带JWT跳转到子系统(如:weather.local/?token=xxxx)
  4. 子系统校验

    • 子系统拦截请求,读取Token
    • 使用本地存储的公钥对Token进行签名验证
    • 验证通过:信任该用户信息,允许进入;同时根据Token中的角色过滤本地菜单
sequenceDiagram
    participant 用户
    participant 中央系统
    participant 子系统

    用户->>中央系统: 登录请求,提交用户凭据
    中央系统->>中央系统: 验证凭据,生成JWT(用私钥签名)
    中央系统-->>用户: 返回JWT token
    用户->>子系统: 跳转并携带token(如 weather.local/?token=xxxx)
    子系统->>子系统: 提取并解析token
    子系统->>子系统: 使用本地公钥验证token签名
    alt 验证通过
        子系统-->>用户: 允许访问
    else 验证失败
        子系统-->>用户: 拒绝访问
    end

5.4 方案评估

优势挑战
完全离线:子系统不需要实时请求中央系统API校验Session撤销困难:如果用户在中央系统退出,子系统的Token在有效期内依然有效(除非引入黑名单,但这又需要联网)
性能高:本地CPU运算,无网络开销密钥管理:公钥泄露可伪造身份(但私钥只要保存在中央系统就是安全的)
部署灵活:子系统可独立打包交付版本兼容:需要妥善处理API版本演进

针对撤销困难的问题,可以采用短时效JWT(如15分钟)+刷新令牌机制;关键权限变更时可维护黑名单(需联网时同步)。

6. 核心表结构

以下ER图对应中央系统「应用+用户+角色」及「菜单/字典由子系统上报」的设计:

  • 中央系统管理子应用(sys_app)、用户(sys_user)及用户在各应用下的角色(sys_user_app_role
  • 子系统的菜单与报表结构由子系统主动上报,存储在sys_app_menu_report
  • 字典按应用维度存放在sys_dict/sys_dict_data

与文中底座型设计、JWT角色下发的思路完全一致。

erDiagram
    %% ========== 应用与扩展配置 ==========
    sys_app {
        text app_code PK "应用编码,子应用唯一标识"
        text app_name "应用名称"
        text public_key "RSA/Ed25519 公钥,供子系统验签 JWT"
        text description "描述"
        text secret_key "私钥存中央系统,用于签发 JWT"
        character status "状态,如 0 停用 1 启用"
        date last_heartbeat "最后心跳,健康状态上报时间"
    }
    sys_app_ext {
        text app_code PK "关联 sys_app"
        text config_key PK "配置键"
        text config_value "配置值"
        boolean is_override "是否被中央系统覆盖"
    }

    %% ========== 用户与应用角色 ==========
    sys_user {
        text user_id PK "用户唯一标识"
        text username "登录名"
        text password_hash "密码哈希"
        text nickname "昵称"
        character status "用户状态"
        text email UK "邮箱,唯一"
        timestamp created_at "创建时间"
        timestamp updated_at "更新时间"
    }
    sys_user_app_role {
        serial id PK "主键"
        text app_code "应用编码"
        text user_id "用户 ID"
        text_array role_key "该用户在此应用下的角色键列表,可写入 JWT"
    }

    %% ========== 子系统上报的菜单/报表 ==========
    sys_app_menu_report {
        serial id PK "主键"
        text app_code "所属应用"
        text menu_key "菜单键"
        text parent_key "父菜单键,层级"
        text title "菜单标题"
        text path "前端路径"
        text icon "图标"
        integer sort_order "排序"
        json meta_data "菜单元数据(metadata),如 require_role 等"
    }

    %% ========== 按应用维度的字典 ==========
    sys_dict {
        serial id PK "主键"
        text dict_name "字典名称"
        text dict_type UK "字典类型,唯一"
        text app_code "所属应用"
        integer status "状态"
    }
    sys_dict_data {
        serial id PK "主键"
        text dict_label "显示标签"
        text dict_value "实际值"
        text css_class "样式类"
        text sort_order "排序(多为数字)"
        integer dict_id "关联 sys_dict.id"
    }

    sys_app ||--o{ sys_app_ext : "扩展配置"
    sys_app ||--o{ sys_app_menu_report : "菜单上报"
    sys_app ||--o{ sys_user_app_role : "应用角色"
    sys_user ||--o{ sys_user_app_role : "用户在某应用的角色"
    sys_app ||--o{ sys_dict : "应用字典"
    sys_dict ||--o{ sys_dict_data : "字典项"

7. 总结

本文完整记录了我对”派生系统快速集成”问题的思考演进过程——从最初的”中央控制系统”设想,到发现其局限性,再到理念革新为”认证基座”模式,最终形成一套支持子系统离线自治的完整方案。

这一演进过程的核心收获是:控制不如赋能。当中央系统放弃对子系统的强控制,转而聚焦于认证与服务发现时,反而获得了更好的灵活性、扩展性和离线能力。

方案的可行性已在理论层面完成验证,后续将通过实际项目落地持续优化。希望本文的思考过程能为面临类似问题的同行提供一些参考。


参考文献

[1] 普拉巴斯·西利瓦德纳. API安全进阶:基于OAuth 2.0框架[M]. 第二版. 2025.