在我过去的工作中,曾遇到一个难题——内部系统越来越多,后台链接散落各地。
随着公司逐步推行微服务架构和私有化部署,多个后台系统逐渐成为日常工作的一部分,且没有统一入口,导致团队成员甚至老板都频繁询问后台地址和登录信息。尤其是在换设备或清理浏览器缓存后,后台入口常常一片迷雾,效率低下且繁琐。
所以,我设计了一个“统一门户管理中心”,用来集中管理所有子系统的登录和权限。
从需求到设计
思路碰撞
首先,明确需求:
- 多后台:公司多个子系统部署,技术栈各异,管理成本巨大。
- 统一入口:让员工登录一次后,便能访问所有有权限的子系统。
- 低成本接入:避免每个子系统都进行大规模重构,减轻开发负担。
我最初考虑了 SSO(单点登录)方案,但发现传统的 SSO 协议虽然广泛应用,但对我们这样的内部系统来说,过于复杂且增加开发成本。
权衡与选择
通过多次对比,我最终决定采用“门户为中心”的方式,在统一入口实现登录,避免对每个子系统进行繁琐的改造。用户在门户登录后,生成一次性授权码,子系统只需用该授权码获取用户信息,并生成本地 token。
核心优势:
- 用户体验:登录一次后,便可访问多个后台,简化流程。
- 低成本接入:无需每个子系统改造,只需轻量级接入。
- 灵活性:适应不同的技术栈(Java、PHP、Go)和环境。
- 安全性:每次登录生成唯一授权码,有效期短,避免重放攻击。
后台设计思路
为了实现统一门户,我将其拆分为以下几个模块:
- 主页(系统入口总览):展示所有可访问的系统入口,用户点击后,通过生成一次性授权码跳转到子系统。
- 用户管理:统一管理公司员工的账号信息、权限设置和安全控制。
- 系统管理(子系统注册):为每个子系统注册信息,包括系统名称、URL、状态等。
- 系统健康检查:定期检查子系统是否在线,确保系统的可用性。
- 用户授权:控制每个员工可以访问哪些后台系统。
- IP白名单控制:增加额外的安全性,限制只有公司网络或特定IP才能访问。
- 操作日志:记录每个操作,方便审计和排查。

无感接入,轻松实现
系统接入
对于每个子系统的接入,我采取了封装SDK的方式。无论是Java、Go、PHP语言的项目,接入都十分简便。只需引入统一的 SDK,配置相关参数,便可自动接入统一门户,实现免登录。
对于第三方工具(如 Jenkins、GitLab),则采用轻量级通行证模式,模拟登录状态,避免改动过多。
单点退出
为了确保一致性和安全性,我在系统中实现了单点退出(SSO Logout)功能。用户在门户退出时,所有相关子系统都能即时响应,强制下线,保证了安全性与用户体验的一致性。
实现:
- 认证中心退出:用户退出时,系统在 Redis 中写入退出标记。
- 子系统拦截器:每个子系统会检查 Redis 中的退出标记,确保用户退出后无法再继续访问。
系统健康监控
健康监控是一个额外但关键的功能。我们利用健康检查接口定时探测每个子系统的在线状态,确保任何子系统宕机时,能第一时间通知管理员,避免误导用户。

高效的接口与管理
后端设计
在后端,我们实现了以下两个关键接口:
- 创建授权码接口(/sso/code/create):生成一次性授权码,供子系统验证使用。
- 校验授权码接口(/sso/code/verify):子系统拿到授权码后,向认证中心验证该码是否有效,并返回用户信息。
1. 创建授权码接口(/sso/code/create)
接口流程:
- 用户在门户点击某个系统卡片,门户向认证中心请求生成授权码。
- 前端接收到授权码后,跳转到子系统。
@Override
public Map<String, Object> createCode(HttpServletRequest request, Map<String, Object> body) {
Long userId = (Long) request.getAttribute("userId");
if (userId == null) {
throw new RuntimeException("未登录或 Token 无效");
}
String appId = body.get("appId").toString();
SysApplication app = applicationMapper.selectByAppId(appId);
if (app == null) throw new RuntimeException("应用不存在:" + appId);
if (!userHasPermission(userId, appId)) {
throw new RuntimeException("用户无权访问系统:" + appId);
}
// 生成授权码
String code = UUID.randomUUID().toString().replace("-", "");
String authRedisKey = SsoRedisKeys.authCode(code);
Map<String, Object> authInfo = new HashMap<>();
authInfo.put("userId", userId);
authInfo.put("appId", appId);
redisUtils.set(authRedisKey, authInfo, 60); // 设置 60 秒过期
Map<String, Object> result = new HashMap<>();
result.put("code", code);
result.put("expire", 60);
return result;
}
2. 校验授权码接口(/sso/code/verify)
接口流程:
- 子系统收到授权码后,向认证中心验证该授权码的有效性,并获取用户信息。
public Map<String, Object> verifyCode(HttpServletRequest request, Map<String, Object> body) {
String code = String.valueOf(body.get("code"));
String appId = String.valueOf(body.get("appId"));
// 校验授权码是否有效
String redisKey = SsoRedisKeys.authCode(code);
Map<String, Object> authInfo = redisUtils.getObject(redisKey, Map.class);
if (authInfo == null) {
throw new RuntimeException("授权码无效或已过期");
}
// 校验 appId
String storedAppId = (String) authInfo.get("appId");
if (!appId.equals(storedAppId)) {
throw new RuntimeException("授权码与应用不匹配");
}
// 查询用户信息
Long userId = Long.parseLong(authInfo.get("userId").toString());
User user = userMapper.selectById(userId);
if (user == null) {
throw new RuntimeException("用户不存在");
}
redisUtils.delete(redisKey); // 使用后删除授权码
Map<String, Object> result = new HashMap<>();
result.put("userId", user.getId());
result.put("username", user.getUsername());
result.put("appId", appId);
return result;
}
通过上述两个接口,门户系统和各子系统之间可以实现快速、轻量的单点登录,同时保持子系统的独立性,且无需复杂的技术栈改造。
数据结构与设计
为确保系统稳定运行,我设计了四张核心数据表:用户信息表、系统分类表、系统信息表和用户授权关系表。这些表实现了账户管理、系统分类、授权分配等关键功能,使得每个模块的管理都能轻松扩展。

总结
通过统一门户 + 轻量化 SSO的设计,我们的系统能够在多后台、多语言、多环境的公司中高效运作。
最重要的是,这套方案的扩展性非常强,后期新系统的接入非常简单,能快速满足公司业务的不断扩展。随着企业规模的增长,我们也可以进一步对系统进行优化,增加更多的功能,如后台导航、系统通知、权限控制等,进一步提升使用体验。
这套方案的关键在于简洁、高效、灵活,适用于大多数中小型企业,尤其是在多技术栈、多后台的环境下,既能统一管理,又不强制改变每个子系统的原有登录体系,确保了系统的独立性与统一性。