派生系统的快速集成:从控制中心到认证基座的设计演进
摘要
本文探讨了企业内部多系统集成的发展历程与架构演进。从最初的信息孤岛现象出发,分析了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)**模式。
最终,中央系统只负责两件事:
- 管理账号
- 下放令牌(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 社区同类实现
实际上,社区中已经有类似的中央系统实现,如Nacos、Apollo、Keycloak等。不过,大部分社区实现采用的是中央系统控制子系统大部分信息的方式——典型的多租户模式,一套代码给上千人使用,配合配置中心负责配置推送和SDK注入。中央系统实现RBAC/ABAC全栈托管,核心目的是配置中心化。
而我的设计思路与上述实现有所不同:更强调子系统的独立性和离线自治能力。
5. 最终架构设计
5.1 核心原则:角色反转
综合以上思考,为了实现快速0-1搭建与快速交付,同时满足子系统断开中央系统后仍能正常运行的弱依赖设计目标,我确定了以下设计原则:
角色反转——从”中央控制”转向”子系统自治+中央观察”:
- 子系统侧(Owner):菜单定义在子系统的代码或本地配置中。子系统最清楚自己有哪些功能。离线时,直接读取本地定义的菜单,完美运行。
- 中央系统侧(Observer/Aggregator):作为一个”展示窗口”。当子系统在线时,主动将菜单结构推送(或由中央系统拉取)给中央系统。
这样做的好处显而易见:
- 彻底解耦:子系统不再是被动接受指令的”木偶”,而是具备独立生命周期的”模块”
- 配置一致性:永远不会出现”中央系统配了菜单,但子系统代码没实现”的尴尬
5.2 配置同步机制
在决定让子系统自备菜单并上报、同时要求离线自治后,中央系统的定位确实发生了根本性变化——从一个”独裁的控制中心”转变为一个”服务发现与资源协同中心”。
具体机制:
- 主动上报:子系统在启动时、或配置变更时,主动向中央系统上报自己的菜单结构、字典数据
- 版本控制:上报API支持版本参数,即使API发生变化,原有接口也不会失效
- 被动接收:中央系统负责接收并存储上报数据,提供统一展示窗口,但不强制下发
- 快照机制:子系统在本地维护配置快照,离线时直接读取快照
5.3 离线验证流程
在离线或弱依赖场景下,采用JWT + 非对称加密是最主流的方案:
-
初始化阶段:
- 中央系统生成密钥对(私钥private.key,公钥public.key)
- 将公钥以配置文件或环境变量的形式,预先分发并部署在每个子系统中
-
登录阶段:
- 用户在中央门户登录,中央系统生成JWT
- JWT载荷中包含用户信息、过期时间、在各应用下的角色列表
- 中央系统用私钥对JWT签名
-
跳转阶段:
- 用户携带JWT跳转到子系统(如:weather.local/?token=xxxx)
-
子系统校验:
- 子系统拦截请求,读取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.