
集成过企业级系统的兄弟应该都有同感用户管理一个地址工作流审批又是另一个地址电脑里存了七八个密码最后全都记错。我之前在项目里做 soular 这套体系时面对的正是这个局面——soular 提供统一基础数据和认证能力Kanass 管后台控制台sward 跑工作流审批三个模块各自有登录入口业务同事天天抱怨“换个功能就让我重新登一次”。这个实践教程的核心就一句话怎么把 soular、Kanass、sward 三件套全部纳入同一套单点登录体系让用户一次认证、处处通行。文章会先讲清楚三者架构分工和 SSO 的核心原理再分别给出认证中心、管理控制台、工作流模块的集成步骤最后把联调过程中最容易踩的坑都列出来。不管是做统一认证迁移的老手还是第一次接手这套平台的新人按这个流程走一遍应该能少走不少弯路。1. 项目整体设计与思路拆解先理清三件套的分工再动手1.1 soular、Kanass、sward 到底各管什么第一次接触这套体系的同学最容易犯的错误就是把它当成三个独立系统来部署然后给每个系统各配一套登录最后又绕回原点。我在做对接时最先做的就是先把各自的职责边界理清楚。soular在这个体系里是底座承担三件核心事用户数据、组织架构、统一认证。也就是说全系统“你是谁、有什么权限”这类信息最终都以 soular 为准。它扮演的不只是后端服务还是认证中心其他模块不直接校验用户名密码而是向 soular 要一个身份凭证。你可以把它理解成整栋大楼的前台所有访客第一站都到它这里登记。Kanass是管理控制台也就是用户日常点开页面看到的那个门户和操作界面。它不保存密码也不负责最终的权限裁决它只做两件事把未登录的人带到认证中心去登录以及拿着 token 去请求业务接口。这个定位想清楚之后后面写路由守卫和回调接口的时候就不会跑偏。sward是工作流服务负责流程定义、发起、审批、流转这些事。它需要知道“当前登录人是谁”因为审批单上要记录发起人和审批人但它同样不关心密码怎么校验而是信任认证中心下发的 token。这里有一个隐含要求sward 必须能解析 token并且把 token 里的用户身份转成自己的业务数据否则流程记录里的“人”就全是空的。这个结构理解到位之后就能明白整个 SSO 的集成方向以 soular 为圆心Kanass 和 sward 都作为客户端接入。登录流程是从前端入口触发但真正校验身份的一定是 soular。后续踩坑时也能快速定位问题出在“认证中心”还是“客户端解析”不会两个系统之间来回猜。1.2 为什么不能继续“各登各的”很多团队一开始觉得三个系统各自登录也不至于用不了为什么要费劲做单点登录。我举几个真实场景你就明白了。第一个权限模型重复维护。同一个用户在 soular、Kanass、sward 里都维护了一份账号离职的时候要删三次漏一次就可能出现“人走了但流程审批还能操作”的事故。做过权限治理的应该知道这种事故一旦发生审计阶段非常被动。第二个密码分布多处。用户习惯同一个密码某个子系统的密码库一旦泄漏其他系统的账号也跟着遭殃。统一在一个认证中心风险就能收敛到一个点上并且集中做防爆破、定期的异常检测。密码泄漏这种事收敛到一个点能避免很多不可控的连锁反应。第三个使用体验差。业务员上班一上午可能要在门户里看数据、在流程系统里提审批、在管理台里做配置。如果每次切换到不同地址都要重新登录一天的时间大半都在输密码实际业务效率会大打折扣。用个最简单的类比这就好比一栋写字楼里几个部门各发各的门禁卡员工要去三个楼层得随身带三张卡。单点登录做的就是“一张卡走全楼”——只在楼下大堂刷一次后面各个房间的门自动识别你是同一个人。企业上 SSO本质上是把“身份”从每个系统里抽出来交给一个更专业的地方统一管理。1.3 方案选型为什么选 OAuth2 授权码模式搞过企业集成的人都知道单点登录的常见方案其实有好几套传统 CAS、OAuth2/OIDC、共享 Session、网关统一签发 JWT。到底选哪个要结合 soular 这套架构的特点来看。我在项目里最终选定的是OAuth2 授权码模式理由有三。第一是角色清晰。soular 天然可以承担授权服务器Kanass、sward 作为受保护的客户端接入服务之间谁也不越界。授权码模式把登录操作全部收敛在认证中心页面子系统的代码里基本不需要写“校验密码”的逻辑每个模块只需要关心“拿到 token 之后怎么用”。第二是安全边界好。授权码模式下用户的用户名密码只在认证中心出现一次Kanass 和 sward 拿到的只是一个临时授权码再拿授权码去后端申请 access_token。用户密码不会被业务子系统摸到风险面大幅缩小。如果哪个子系统被拖库攻击者拿到的也只是 token而不是口令。第三是可扩展性强。以后如果要接入企业微信、钉钉、飞书这类外部身份源OAuth2/OIDC 几乎是最主流的协议不需要推翻重来。很多企业后来都发现账号体系不是只有内部一套外部员工的协同身份也要打通提前选对协议能省下重构成本。如果你只是想在 soular 内部把 Kanass 和 sward 串起来暂时不接任何外部系统也可以退一步用“认证中心签发 JWT、子模块验签”的轻量方式。但从我在实际项目里的经验看既然要搭一套认证体系直接用授权码模式其实更省事后面接外部身份源、接第三方系统都是水到渠成的事不用二次改造。2. 单点登录的核心原理一次认证如何做到处处放行2.1 登录真正解决的两个问题认证与授权很多人把单点登录想象得很玄乎其实它解决的问题就两个你是谁你能干什么。在技术上分别对应认证Authentication和授权Authorization。认证就是确认用户确实是本人方式是输入密码、扫码、短信验证码等等。授权是在确认身份之后判断这个人能访问哪些模块、哪些接口。在 soular 体系里认证通常由 soular 完成授权则会在认证中心、Kanass 控制台、sward 工作流三个层面分别落地。这里有个关键点单点登录解决的是“一次认证”的问题不意味着一次认证之后就什么都能干。用户登录后能访问哪些页面、能审批哪些单据仍然由各模块自己的权限配置决定。所以别把 SSO 等同于“权限共享”这两个层次要分开理解。我做项目时就见过有人把 SSO 接通后默认所有接口都放行后面被安全审计揪出来整改相当麻烦。2.2 授权码模式的完整流程拆解授权码模式虽然名字有点技术腔但流程并不复杂。我按下真实发生的顺序拆给你看。第一步用户访问 Kanass 门户前端发现本地没有有效身份凭证于是把页面重定向到 soular 认证中心的登录地址同时带上 client_id、回调地址 redirect_uri、请求的 scope 这些参数。第二步soular 认证中心展示登录页用户输入用户名和密码。认证中心校验通过后不会直接把 token 给浏览器而是先下发一个短期有效的授权码然后带着这个授权码重定向回 Kanass 的回调地址。第三步Kanass 的后端服务收到授权码在服务端拿着这个授权码加上自己的 client_secret再一次向 soular 换取 access_token 和 refresh_token。第四步Kanass 拿着 access_token 去请求各种资源接口soular 和 sward 在收到请求时校验 token 合法有效就返回数据。这个流程里最值得留意的设计就是第三步。授权码交换 token 的动作必须在服务端完成因为这一步会用到 client_secret它相当于客户端的密码绝不能出现在浏览器里。浏览器只负责“搬运”授权码不接触 client_secret所以即使前端被人扒开调试也拿不到签发 token 的完整凭据。这个模式的逻辑可以用一个生活类比来理解你去酒店前台办入住前台给你一张临时门卡授权码你拿着门卡去房间楼层时再通过酒店内部系统把这个临时卡换成正式房卡token。整个过程里你不会拿到酒店的万能钥匙。2.3 access_token 和 refresh_token 为什么要分开第一次做集成的人经常有一个疑问既然 access_token 能访问资源为什么还要一个 refresh_token 多此一举。原因是安全。access_token 是使用最频繁的凭证每次请求都要带因此暴露在日志、代理、浏览器开发者工具里的概率最大。为了避免 token 被偷走后长时间有效造成大范围风险业界通行做法是把 access_token 有效期设短可能是二十分钟、一个小时。但有效期太短又会让用户体验变差过一小时就被迫重新登录一次很难受。refresh_token 就是用来解决这个矛盾的。access_token 过期之后客户端拿着 refresh_token 再去认证中心换一个新的 access_token用户全程无感知。refresh_token 的权限范围通常比 access_token 更有限它只能用来换取新 token不能直接访问业务接口。并且它的生命周期更长也需要更严格地保管。在 soular 这套体系里如果你们用了 Redis 这类集中式缓存建议把 refresh_token 和用户会话的关联都放在 Redis 里方便统一管理失效和踢人下线。否则用户如果已经离职只要 refresh_token 还在有效期内理论上还能续上新的 access_token这就是一个安全黑洞。3. soular 统一认证中心接入实操注册客户端与用户体系对接3.1 注册 Kanass 和 sward 两个 OAuth2 客户端接入 SSO 的第一步不是写代码而是在 soular 认证中心里把“谁能接入”先定义好。这个环节对应 OAuth2 里的客户端注册本质上是给每个子系统发一张“门禁卡”并规定这张卡能开哪些门。以常见的配置模型为例Kanass 和 sward 分别对应一条客户端记录。我把关键字段列一下字段Kanass 示例sward 示例说明client_idkanass-consolesward-workflow客户端唯一标识client_secret随机长字符串随机长字符串客户端密码服务端保管authorized_grant_typesauthorization_code, refresh_tokenauthorization_code, refresh_token允许的授权方式scoperead, writeread请求的权限范围redirect_urihttps://kanass.example.com/callbackhttps://sward.example.com/callback授权后回调地址这里最容易出错的是redirect_uri。OAuth2 里要求这个地址必须跟注册时完全一致协议、域名、路径、端口都不能差。我在实际项目里见过好几次前端回调地址注册的是 https实际跳转时用了 http导致认证中心直接拒绝排查半天才发现是这里不一致。client_secret 的生成建议用系统随机生成不要手打。一个安全的 secret 应该有足够长度和随机性至少 32 字节以上。sward 这类服务端组件可以把 secret 配在配置文件里Kanass 控制台如果是纯前端应用要注意 secret 只能放在后端调用不能打进前端代码。这个原则和“授权码必须在服务端换 token”是同一个道理都是为了防止密钥落入浏览器环境。3.2 对接已有用户体系适配器模式实际项目里有一个问题几乎必然遇到用户数据已经在旧系统、企业微信或者某种内部账号库里了soular 不可能要求用户重新注册一遍。这个时候需要做的是用户体系对接。在 Spring 技术栈的常见实现里认证中心会提供一个 UserDetailsService 的适配接口soular 通过这个接口去拉取用户信息。适配器要做的工作有两件一是按登录名找到用户二是校验密码或第三方身份凭证。如果对接的是企业微信或钉钉流程会从“输入密码”变成“扫码后回调”认证中心接收到第三方返回的临时凭证后去第三方接口换取用户标识再映射到本地用户。映射关系里最关键的一点是要维护一个稳定的唯一标识比如用户主键 user_id而不是用手机号或邮箱。因为手机号可能变更邮箱也可能变更但平台内的用户主键一旦生成就不应该变。我见过很多系统最后出问题都是因为全程拿“用户名”当唯一键用户改了一次登录名历史审批记录、流程发起人全都对不上了。这个教训在接入 SSO 时尤其值得提前规避业务表关联身份时优先用 user_id显示层再冗余一个用户名快照这样即使以后改用户名历史数据也不会乱。3.3 密钥生成与 JWT 签名算法选型如果 token 采用 JWT 格式签名算法选择就是安全的核心。我的建议很明确用 RS256不要用 HS256。HS256 是同一个密钥既负责签发又负责验证。这意味着如果哪个子系统拿到了这个密钥它就能自己伪造 token冒充任意用户访问其他子系统。在 soular、Kanass、sward 分散部署的架构里这个风险是无法接受的。RS256 采用非对称加密soular 用私钥签发 tokenKanass 和 sward 只需要保存公钥来验证 token 签名。私钥始终只存在认证中心任何子系统都无法伪造身份顶多验证真伪。这就好比公章只放在总部各个分部只能看盖出来的章是不是真的但不能自己伪造。如果你需要手动生成密钥对用 OpenSSL 就能完成# 生成私钥 openssl genrsa -out private.pem 2048 # 从私钥导出公钥 openssl rsa -in private.pem -pubout -out public.pem把私钥配置在 soular 认证中心公钥分发给需要校验 token 的 Kanass 和 sward。需要注意私钥文件的权限要严格控制不要提交到 Git 仓库也不要打进 Docker 镜像里。临时调试时用软链或者环境变量注入生产环境建议使用密钥管理服务统一托管。密钥轮换也要提前想好定期更换密钥对时要给旧公钥留一段过渡期不然所有已签发的 token 会在换钥瞬间全部失效造成大面积登录故障。3.4 会话保持与缓存设计单点登录有一个隐藏的体验点用户第一次在认证中心登录后再访问 sward 工作流模块时不应该要求他再输入一次密码。这就要求认证中心能记住“这个用户在某个浏览器里已经登录过”。通常的做法是把登录会话放入 Redis 这类集中式缓存通过 Cookie 里的 sessionId 来识别。只要同一个浏览器在访问 Kanass 和 sward 时cookie 都能被带到认证中心对应的域名下用户就不会被重复要求登录。这里要注意的是Cookie 的域名和 SameSite 属性。如果 Kanass 和 sward 和认证中心不在同一个一级域名下Cookie 是无法跨域带过去的这会直接变成每次都要求登录。常见解法是让三个模块共用一个一级域名比如 kanass.example.com、sso.example.com、workflow.example.comCookie 设置在 example.com 域下。在本地开发环境如果涉及跨域也可以临时放宽 SameSite 配置但生产环境建议保持严格限制。另外Redis 中建议同时存三样东西已签发的 refresh_token 和用户会话映射、已注销的 token 黑名单、登录失败次数的计数。最后一项可以在暴力破解时用来做临时锁定虽然这不是 SSO 的核心需求但既然认证中心已经集中了不做白不做。4. Kanass 管理控制台接入 SSO 的详细操作4.1 前端路由拦截与登录跳转Kanass 作为用户操作的主要前端接入 SSO 后要做的第一件事就是“拦”没有登录凭证的统一送去认证中心。以典型的前端工程为例通常会做一个路由守卫。每次页面跳转前检查本地是否存在 access_token没有就组装认证中心地址并跳转。判断依据也可以加上 token 有效期只是简单判断“存在”并不够。有效期可以由后端接口返回 401 时再做二次处理但前端先判断有效期的好处是能减少一次必然失败的网络请求。认证中心的跳转地址大致是这样一个 URLhttps://sso.soular.example/oauth/authorize?client_idkanass-consoleresponse_typecoderedirect_urihttps%3A%2F%2Fkanass.example.com%2Fcallbackscoperead%20writestate随机字符串state 参数容易被忽略但它其实挺重要。它是一串随机值认证中心在回调时会原样带回来前端或后端可以用来校验回调来源是不是上次那个发起的浏览器防止被恶意第三方伪造回调。这个参数在正式环境一定要用上不然你的回调接口可能变成别人执行登录注入的入口。回调地址 redirect_uri 必须是可访问的地址不能是前端路由里的虚拟路径。很多坑都出在这里回调地址写成了 Vue Router 里的 /callback结果认证中心跳回来时服务器根本接不到一直报 404。正确做法是单独提供一个后端接口承接回调处理完再跳