尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MaxKey与JeecgBoot单点登录对接实战:OIDC协议与账户同步全解析

MaxKey与JeecgBoot单点登录对接实战:OIDC协议与账户同步全解析 如果你们公司内部跑着五六个系统每个系统一套账号密码每天早上光登录就要花五分钟那这篇文章就是写给你的。单点登录这个概念喊了很多年真正落地的时候还是有不少细节要抠尤其是当业务系统用的是 JeecgBoot 这类低代码平台而统一认证服务想用 MaxKey 这种开源方案时配置过程远没有想象中那么“填个回调地址就行”。这篇文章我打算按照一个完整的实施路径来讲先解释这套东西里面每个角色的定位再带你完成 MaxKey 和 JeecgBoot 的部署对接最后重点讲账户同步的三种落地方式和我在实际项目里踩过的那些坑。内容偏实战适合企业内部做系统集成的开发、运维和架构师参考如果你只是想把两套系统打通体验一下照着抄也能跑通。1. 先搞明白单点登录里各个角色在干什么1.1 什么是 IdP 和 SP很多人一听到单点登录就想到 CAS、想到 OAuth但真正动手之前先要把两个概念过一遍IdPIdentity Provider身份提供方和 SPService Provider服务提供方。IdP 是负责“证明你是谁”的系统。用户输入一次账号密码IdP 验证通过后发一张“通行证”后续所有接入的系统只要认这张通行证就不再要求用户重复输入密码。MaxKey 在这套架构里就是 IdP。SP 是实际提供业务的系统。用户在 IdP 拿到通行证之后访问 JeecgBoot 时JeecgBoot 会把用户引导到 IdP 校验通行证校验通过后放行。JeecgBoot 在这套架构里就是 SP。这个模型很多人觉得抽象你用现实生活类比一下就明白了IdP 相当于机场安检口你安检一次拿到登机牌SP 相当于各个登机口地勤看到你的登机牌就让你上飞机你不会在每一个登机口都被重新安检一遍。1.2 为什么选 MaxKey 和 OIDC 协议现在市面上的 IdP 方案很多商业的有各类云身份平台开源的也有不少选择。MaxKey 能进入很多国产化项目的选型视野主要是因为几个原因社区版功能完整支持 OAuth2.0、OIDC、CAS、SAML 等多种协议部署方式灵活内存版可以快速体验正式环境也能用 Docker 或独立 Tomcat 部署用户源可以对接 JDBC、LDAP 甚至自定义 SQL这对我们后面做账户同步非常关键。协议选型上我明确建议用 OIDCOpenID Connect而不是 CAS 或者 SAML。原因有三个。第一OIDC 基于 OAuth2.0 扩展是目前 Web 应用对接生态最好的协议JeecgBoot 这种 Spring Boot 技术栈对 OIDC 的支持工具链非常成熟。第二OIDC 返回的 ID Token 本身就是 JWT里面直接携带用户标识、邮箱、姓名等信息业务系统不需要再额外调接口查用户详情。第三同源的 OAuth2.0 授权码流程对前端 Vue3 应用也很友好跳转参数清晰排错方便。如果你还在用 CAS 协议做过对接应该能体会那种“认证完还要再调一次 serviceValidate 接口换用户信息”的痛苦OIDC 把这一层省掉了。1.3 JeecgBoot 在这套架构里的实际位置JeecgBoot 是典型的 Spring Boot 单体或微服务应用有自己独立的用户表 sys_user、角色权限体系和 Token 认证机制。它不是一个天生就能“开箱即用”对接任意 OIDC 的平台默认提供的第三方登录更多是针对微信、钉钉、企业微信这类国内渠道做的开箱支持。所以在 MaxKey JeecgBoot 的对接里JeecgBoot 的角色比较特殊它既要接收 MaxKey 传来的身份信息又要维护自己体系内的用户记录和权限关系。通俗地说MaxKey 负责“验明正身”JeecgBoot 负责“本地接待”。用户是什么角色、能在 JeecgBoot 里看到什么菜单这些仍然由 JeecgBoot 自己管。这个定位决定了我们后面做账户同步时的思路身份认证打通只是第一步用户数据怎么保持一致才是真正的重头戏。2. 环境准备和整体部署规划2.1 版本选择指南动手前先确认版本这块踩坑成本最低。MaxKey 目前社区版已经迭代到 3.x建议直接使用官方最新稳定版。JeecgBoot 建议使用 3.x 版本Vue3 前端那套框架对 OIDC 跳转的处理比老版 Vue2 要清晰一些。还有一个需要注意的地方MaxKey 社区版有两种形态一种是内置 H2 数据库的内存版适合快速体验另一种是配合外部 MySQL 或 PostgreSQL 的正式部署版。我强烈建议即使只是测试也用 MySQL 版因为 H2 里配置的用户数据一旦重启可能丢失等你好不容易把 JeecgBoot 对接调通结果 MaxKey 里配置的应用和用户没了心态容易崩。2.2 MaxKey 安装部署MaxKey 的部署网上教程不少我这里只说关键步骤。环境要求是先装好 JDK 8、MySQL 5.7、Redis看版本选装。如果你机器上已经有 JeecgBoot 的开发环境那 JDK、Maven、Node 这些基础组件大概率齐了MySQL 也可能已经在跑这一步会省很多时间。以下是基于 Docker 的快速部署参考# 先拉取镜像具体镜像名以官方仓库为准 docker pull maxkeytop/maxkey # 运行容器把管理端口映射出来 docker run -d --name maxkey \ -p 8088:80 \ maxkeytop/maxkey启动完成后浏览器访问http://服务器IP:8088会进入 MaxKey 的初始化引导页。首次访问会让你设置管理员密码然后进入管理控制台。控制台里主要关注两个菜单应用管理和用户管理。应用管理负责任务系统接入用户管理负责统一账号体系。2.3 JeecgBoot 的基础环境准备JeecgBoot 这边的准备工作反而简单因为大部分同学手上已经有跑起来的项目了。如果你是从零开始需要先准备一个可运行的 JeecgBoot 单体版前端能正常启动能访问登录页。需要提醒的是JeecgBoot 初始化过程涉及数据库脚本导入、Redis 启动、前端依赖安装这些步骤在官方文档里已经很详细这里不再重复。真正要花心思的是回调地址的规划。整个单点登录流程里用户的浏览器会经历“JeecgBoot 登录页 - MaxKey 认证页 - 回到 JeecgBoot”的跳转路径这三个环节里回调地址必须提前定好否则后面会反复出现“redirect_uri 不匹配”的报错。2.4 回调域名规划的坑回调地址这件事我见过太多团队在这里栽跟头。本地联调时JeecgBoot 前端跑在http://localhost:3000后端跑在http://localhost:8080MaxKey 跑在另一台机器或另一个端口如果直接拿 IP 加端口去配问题不大但一旦上了测试环境域名一多很多人就开始乱了。我建议在规划阶段就统一用域名形式比如前端地址http://sso.test.com:3000MaxKey 地址http://auth.test.com:8088回调地址http://sso.test.com:3000/oidc/callback配置时不要放过任何一个端口和斜杠。MaxKey 端配置的回调地址必须和 JeecgBoot 前端实际发起回调的地址完全一致一个字符都不能差。3. 在 MaxKey 中创建单点登录应用3.1 配置 OIDC 客户端登录 MaxKey 管理控制台进入应用管理点击新增应用协议类型选择 OIDCOpenID Connect。接下来要填一系列参数核心的就那几个应用名称建议用 JeecgBoot 关联的实际业务名称方便后续维护。授权类型选 Authorization Code授权码模式这是 Web 应用最标准也最安全的模式。重定向地址就是上一节规划好的前端回调地址必须完整填到路径末尾。签名算法用 RS256这是 JWT 最常用的非对称签名算法MaxKey 跟 JeecgBoot 之间不需要共享密钥安全性更好。配置完成后MaxKey 会生成一对客户端 ID 和客户端密钥。这两个值后面要填到 JeecgBoot 的配置里记好但不要随手发到群里。3.2 授权范围与字段映射OIDC 的授权范围Scope决定了 MaxKey 在令牌里携带多少用户信息。最基础的是openid必选再加profile获取姓名、头像加email获取邮箱。字段映射是容易被忽略的细节。MaxKey 自己的用户表里字段名和 JeecgBoot 的 sys_user 表肯定不一致比如 MaxKey 里叫displayName的字段JeecgBoot 里对应的是realname。在 MaxKey 端的“属性映射”配置里你可以把 Id Token 返回的键名定义好比如sub - username displayName - realname email - email这样后面 JeecgBoot 解析令牌的时候直接从固定键名里取值不用再写一堆字段转换逻辑。3.3 发布前自测用浏览器直接调试授权链在动 JeecgBoot 代码之前我强烈建议先手动测试一下 MaxKey 的授权链是否正常。方法很简单直接在浏览器地址栏访问 MaxKey 的认证端点把自己当成 JeecgBoot手动走一遍流程。http://auth.test.com:8088/auth/authorize? client_id你的客户端ID response_typecode scopeopenid profile email redirect_urihttp://sso.test.com:3000/oidc/callback如果配置没问题MaxKey 会跳到登录页登录成功后浏览器地址栏会跳转到回调地址并携带一个 code 参数。这个 code 就是授权码后面换令牌要用。这一步自测能提前暴露很多问题域名端口不一致、协议没对上、应用状态未启用、用户没绑定应用权限等。提前花五分钟测一遍后面能少折腾半天。4. JeecgBoot 对接 MaxKey 的完整实现4.1 扩展 OIDC 登录入口JeecgBoot 的登录流程默认只走本地账号密码验证。要接入 MaxKey最干净的做法是在 JeecgBoot 里新增一个 OIDC 登录入口不影响原有的账号密码登录。路径设计上我建议前端直接提供一个“统一身份认证登录”按钮点击后跳转到 MaxKey 的授权端点。后端需要新增一个 Controller暴露两个接口Controller RequestMapping(/sys/oidc) public class OidcLoginController { // 1. 发起登录跳转到 MaxKey 认证页 GetMapping(/login) public void login(HttpServletResponse response) throws IOException { String authorizeUrl http://auth.test.com:8088/auth/authorize ?client_id clientId response_typecode scopeopenid profile email redirect_uri redirectUri; response.sendRedirect(authorizeUrl); } // 2. 回调接口处理 MaxKey 返回的 code GetMapping(/callback) public String callback(String code) { // 后续步骤在此展开 return redirect:http://sso.test.com:3000/oidc/callback?code code; } }这里要说明一下前端和后端分离的架构下最简单的做法是让 MaxKey 直接回调前端页面再由前端把 code 转发给后端接口处理。这样可以少一层跳转也方便前端控制登录后的跳转路径。4.2 回调处理换 token、解析用户拿到授权码之后后端要做的事情就是拿着 code 去 MaxKey 的 Token 端点换访问令牌和 ID Token。这一步用 Spring 的 RestTemplate 就能完成不需要额外引入重量级框架。RestTemplate restTemplate new RestTemplate(); // 组装换 token 的请求 MultiValueMapString, String params new LinkedMultiValueMap(); params.add(grant_type, authorization_code); params.add(code, code); params.add(redirect_uri, redirectUri); params.add(client_id, clientId); params.add(client_secret, clientSecret); HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); // 请求 MaxKey 的 token 端点 ResponseEntityMap response restTemplate.exchange( http://auth.test.com:8088/auth/token, HttpMethod.POST, new HttpEntity(params, headers), Map.class ); String idToken response.getBody().get(id_token).toString();ID Token 是 JWT 格式直接 Base64 解码中间那段就能拿到用户信息。推荐用 jjwt 或 nimbus 库解析顺便校验签名不要手写 Base64 解码了事生产环境还是要严谨一些。// 校验并解析 ID Token JwsClaims jws Jwts.parserBuilder() .setSigningKey(getPublicKeyFromMaxKey()) .build() .parseClaimsJws(idToken); String username jws.getBody().get(preferred_username, String.class); String realname jws.getBody().get(displayName, String.class); String email jws.getBody().get(email, String.class);4.3 本地用户建立和登录态写入这是整个对接里最关键的一步用户已经在 MaxKey 里验证通过了但 JeecgBoot 的 sys_user 表里不一定有这个人。如果不处理就会出现“MaxKey 登录成功JeecgBoot 却进不去”的尴尬。我的做法是在回调处理里加一个用户同步逻辑先查 sys_user 表看有没有对应用户名的记录没有就自动创建密码字段设置成随机值并标记为“不可本地登录”只允许通过单点登录进入有就直接复用原有账户顺便更新一下最近登录时间和邮箱等基础信息。SysUser user sysUserService.getUserByName(username); if (user null) { user new SysUser(); user.setUsername(username); user.setRealname(realname); user.setEmail(email); user.setPassword(UUID.randomUUID().toString()); // 不开放本地登录 user.setStatus(1); sysUserService.saveUser(user); }账户建好之后剩下的就是写入登录态。JeecgBoot 自己有一套 Token 生成和鉴权机制你只需要在 OIDC 回调流程里复用它的 Token 生成逻辑把生成的 Token 通过前端 URL 参数带回 Vue3 页面前端存到 localStorage 里后续请求照常携带即可。4.4 前端 Vue3 跳转与 token 传递前端这边在登录页放一个“统一认证登录”按钮点击后直接改地址栏const loginWithOidc () { const authorizeUrl http://auth.test.com:8088/auth/authorize?client_id${clientId}response_typecodescopeopenid profile emailredirect_uri${encodeURIComponent(callbackUrl)}; window.location.href authorizeUrl; };回调页面加载时解析 URL 里的 code 参数然后调后端接口完成换 token 和登录态写入最终拿到 JeecgBoot 的 Token 后跳转到首页。这个流程用户感知就是点了一下按钮跳到 MaxKey 输了一次账号密码又自动跳回 JeecgBoot然后就已经登录了。5. 账户同步的三种落地方式与选择5.1 OIDC 自动建号模式上一步里我写的自动建号逻辑本身就是一种账户同步方案。这种模式的核心特征是以 MaxKey 为唯一认证源JeecgBoot 不主动维护密码只在用户首次通过单点登录进入时自动建档。这种方案的优点很明显实现简单不需要额外的同步任务天然保证“能通过单点登录进来的用户在 JeecgBoot 一定有账号”。缺点同样明显JeecgBoot 侧的角色、部门、岗位等业务属性无法自动同步需要管理员在 JeecgBoot 里二次分配权限。所以这种模式适合业务系统数量不多、每个系统内权限结构各有特点的场景。它解决的是“能不能进来”的问题把“进来能干什么”留给各业务系统自己管符合企业内部系统“统一认证、分布授权”的常见治理思路。5.2 JDBC 用户源直连模式MaxKey 管理后台里有一项“用户源配置”支持配置 JDBC 连接器直接从外部数据库读取用户列表。也就是说MaxKey 可以不把用户存在自己的表里而是直接连到 JeecgBoot 的数据库读 sys_user 表。这种模式适合已经以 JeecgBoot 为用户数据中心的场景。配置时需要在 MaxKey 里写一条数据源 SQL让 MaxKey 用这个 SQL 查询用户SELECT username, realname, email, status FROM sys_user WHERE status 1但这里要注意用户密码校验的时候MaxKey 拿到你查出来的用户记录后还需要一个密码字段来做比对。而 JeecgBoot 的密码是 BCrypt 加密的如果 MaxKey 的加密算法配置对不上即使数据查出来了密码校验也会失败。所以这个方案在实际落地时往往还需要在 JeecgBoot 用户表里额外维护一个 MaxKey 能识别的密码字段或者改用 MaxKey 作为密码校验方、JeecgBoot 用户只负责业务属性。这需要两边的密码策略统一实施成本不低。5.3 定时接口同步模式既然用 JDBC 直连有密码算法冲突的问题又想在 JeecgBoot 侧保留完整的用户管理和密码体系那可以考虑定时同步模式。思路很简单用定时任务定期把 MaxKey 里的用户列表拉下来比对后写入 JeecgBoot 的 sys_user 表。实现方式有很多种MaxKey 本身提供用户管理的 API你可以在 JeecgBoot 里写一个定时任务按小时或按天调用 MaxKey 接口拉取用户也可以反过来在 MaxKey 侧配置事件通知或手动导出的方式把用户变更推给 JeecgBoot。这种模式的灵活性最高两边系统完全解耦用户数据可以按你需要的方式做字段映射和清洗。代价是要写代码、要部署定时任务还要处理增量同步和删除同步的复杂逻辑。如果团队排期紧张可以先做全量同步后续再优化增量逻辑。5.4 方案选型建议方案同步方向实现成本适用场景OIDC 自动建号MaxKey - JeecgBoot低系统少各系统权限独立JDBC 用户源直连JeecgBoot - MaxKey中以 JeecgBoot 为用户中心定时接口同步双向高系统多需要字段级管控从我的经验来看大部分团队第一次落地时选择 OIDC 自动建号是最稳妥的。先把认证打通让用户能进系统后面再根据实际管理需求上定时同步这种渐进式的实施方式比一口气把同步逻辑做完更可控。6. 高频问题与排错实录6.1 回调进不去登录就报错如果你点击登录后跳转到 MaxKey 输入账号密码MaxKey 页面提示回调地址错误或者跳回 JeecgBoot 时直接 404那 90% 的问题是 redirect_uri 不一致。排查思路很直接打开浏览器 F12在 Network 里看到跳回 JeecgBoot 的完整地址然后去 MaxKey 的应用配置里比对看看是不是多了个斜杠、端口写错、或者路径大小写不一致。还有一种是前端用了 encodeURIComponent 编码MaxKey 端配置的是解码后的地址两边看起来一样实际不一样。这类问题我在项目里排查过太多回最后总结出一个笨办法配置回调地址时直接复制浏览器地址栏里的完整地址粘贴到 MaxKey不要手敲。6.2 token 解密失败或用户信息缺失JWT 解析报签名错误先检查 MaxKey 端的签名算法是否和 JeecgBoot 端代码里用的一致。默认建议 RS256但如果你在 MaxKey 里改成了 HS256那 JeecgBoot 这边也需要对应调整成对称密钥解密这个很容易漏。还有一种情况是 ID Token 里取不到某个字段。MaxKey 只会在 Token 里携带你在授权范围里申请的字段。如果 JeecgBoot 解析的是preferred_username但 MaxKey 返回的字段名实际上是username就会拿到空值。遇到这种情况先去 MaxKey 管理后台的用户属性配置里确认字段名再去比对代码里取的键名。6.3 组织架构和字段对不上这是账户同步里最让人头疼的问题不是技术问题是数据标准问题。A 系统里叫“张三”B 系统里叫“zhangsan”A 系统的手机号是 11 位B 系统里多了个区号。这些脏数据在做同步映射时全都会冒出来。我的建议是在做账户同步之前先整理一份字段映射表把 MaxKey 和 JeecgBoot 两边所有要同步的字段列出来逐一确认语义和格式。这是最枯燥但最值得做的事情。字段映射表没理清之前写多少同步代码都是白搭。6.4 单点登出与会话清理最后一个容易被忽略的问题是登出。用户从 JeecgBoot 退出只是清了 JeecgBoot 的会话如果 MaxKey 侧的会话还在用户再次访问 JeecgBoot 时可能又被自动登录进来。要真正做到“一处退出、处处退出”需要走 OIDC 的登出流程让 JeecgBoot 退出时同时调用 MaxKey 的登出端点。这块 MaxKey 支持但需要你在 JeecgBoot 的退出逻辑里手动加一步很多团队图省事跳过这一步导致用户以为退出了实际上换个入口又能进系统。按我个人的实施习惯单点登录上线第一周就把登出联调测完不要拖。因为用户对“退出后还能被别人轻易进入”的容忍度非常低这个问题晚暴露不如早暴露。最后再分享一个经验。MaxKey 和 JeecgBoot 的对接本质上不是一个纯技术问题而是一个“谁能管理用户、谁负责鉴权、数据以谁为准”的权责问题。技术方案有很多种但适合你们团队的方案往往取决于现有的组织架构和数据归口管理方式。建议在动手之前先花半天时间和相关同事对齐这三个问题后面每一步都会走得很顺。
返回列表