最近在公司做内部系统整合手里一个业务后台叫 Kanass要跟内部的统一认证平台 soular 打通目标是实现所有系统一次登录到处访问。折腾了一圈把 OAuth2 授权码流程、回调校验、令牌刷新、跨域 Cookie 这些点挨个踩了一遍最终跑通。整个过程比想象中要琐碎但理清之后其实很规整。如果你也在给自研系统接统一登录可以参考这套做法。这里我不讲泛泛的概念直接拿 Kanass 对接 soular 这个真实场景拆解。整个联调过程分了三个层面第一是设计层面明确 soular 和 Kanass 各自的边界第二是配置层面把 soular 侧的应用注册和 Kanass 侧的认证源配好第三是代码层面处理回调、换取 token、保存会话。围绕这三层我整理了完整的操作步骤、参数对照以及我在实际环境中遇到的坑。适合看的同学后端开发、前端负责人、运维或者任何需要给自己的系统接统一身份认证的人。不需要你之前用过 soular但最好知道 OAuth2 大概长什么样完全没概念的话第二部分的解释可以帮你补上。1. 项目背景与整体思路在做 Kanass 对接 soular 之前内部已经跑了三年多的老账号系统。每个系统都有一套自己的用户表员工入职要分别申请账号离职要逐个注销安全审计的时候账号清单对不上。这次目标很明确把 Kanass 这类业务应用全部统一到 soular 上用户只要在 soular 登录一次进 Kanass 就不用再输入账号密码。这是典型的 SSO 场景但 SSO 不是简单把登录页搬过去它涉及协议、令牌、回调、会话管理一串东西。1.1 为什么一定要让 Kanass 和 soular 打通先说业务痛点。Kanass 是一个内部管理系统里面挂了权限、审批、报表模块原来自己做了一套登录逻辑数据库里存用户表和密码摘要。问题是 Kanass 的账号体系和别的系统互不相通每个系统都在重复建设账号模块。用户记密码难管理员重置密码难安全团队想做统一风控也无从下手。打通以后账号数据只在 soular 维护Kanass 不再关心密码怎么校验只关心当前请求的用户是谁、有没有权限。soular 登录成功后浏览器会带着一个有效的认证凭证Kanass 通过协议拿到用户身份再建立自己的业务会话。这样新增一个应用时不需要重新设计用户系统只需要按标准协议接入即可。还有一个容易被忽略的原因统一登录之后退出登录也能统一。以前每个系统有独立的退出按钮用户以为退出了公司账号其实只是退出了一个子系统。soular 接入后可以由认证中心统一发起退出Kanass 本地会话一并清理安全边界更清晰。1.2 Kanass 和 soular 的职责划分明确边界是这个项目里最关键的事。soular 是认证服务端负责的事包括登录页面展示、账号密码校验、会话保持、授权码下发、令牌颁发、用户信息提供。它不关心 Kanass 的业务数据也不应该碰 Kanass 的业务表。Kanass 是业务应用负责的事包括引导用户跳转到 soular、接收回调解码授权码、换取访问令牌、读取用户信息、建立本地登录态、做权限控制。它不保存密码也不能私自信任一个前端传递的用户 ID所有身份来源必须经过 soular 的令牌校验。这个边界直接决定代码放到哪一层。登录页必须由 soular 控制Kanass 不能自己画一个登录页再偷偷传给 soular否则账号密码会经过业务系统又回到老问题的老路。Kanass 只保留“登录控制器”也就是把请求重定向到 soular 的入口以及处理 soular 回调回来的令牌。1.3 为什么选 OAuth2/OIDC 这类标准协议方案选型的时候考虑过自己写一个简单的 token 方案用户登录 soular 后生成一个随机字符串存 RedisKanass 拿着这个字符串去 soular 校验。听起来简单但问题是每个系统对接方式都不一样后续接入新应用得重复造轮子安全性也没法统一保证。后来决定走 OAuth2 标准协议并在此基础上用 OIDC 获取身份信息。原因是生态成熟Java、Go、Node 都有现成客户端库soular 侧也有标准端点。Kanass 接入时只需要实现两次 HTTP 请求一次拿授权码一次拿令牌代码量很少。具体流程是标准的授权码模式Kanass 跳转到 soular 的授权端点用户登录后 soular 回调 Kanass 的 redirect_uri在 URL 里带一个 codeKanass 后端用这个 code 加上 client_id、client_secret去 soular 的 token 端点换取 access_token 和 id_token最后再用 access_token 调用户信息端点拿到用户唯一标识。整个链路中只有 Kanass 后端持有 client_secret前端页面全程接触不到安全性有基本保障。提示如果你的系统要求强身份令牌建议直接启用 OIDC业务系统可以通过 id_token 里的标准声明拿到用户 ID、邮箱、姓名省去一次用户信息接口调用。2. 核心概念拆解从会话到令牌很多同学第一次接统一登录会把注意力放在“跳转”上以为只要把登录按钮指到 soular 就行。实际不是这样。跳转只是入口真正核心的是两个概念会话怎么建令牌怎么验。搞懂这两个后面配置和排错都会顺畅很多。2.1 会话、Cookie 与 SSO 的本质soular 登录成功后会在自己的域名下种一个 Cookie这个 Cookie 是认证中心的会话凭证。因为 Cookie 是域名级别的所以 soular 能识别“这个浏览器是否已经登录”。但 Kanass 的业务域名跟 soular 不一样浏览器不会把 soular 的 Cookie 自动发给 Kanass所以 Kanass 不能直接复用这个会话。这就是授权码模式存在的意义。用户访问 Kanass 受保护页面如果 Kanass 发现本地没有会话就先把用户送到 soular。soular 检查到认证中心会话有效就不展示登录页而是直接生成一个授权码通过回调地址把用户带回 Kanass。Kanass 拿到授权码以后再换令牌、建自己的会话。可以用门卫模型去理解。soular 是公司门卫Kanass 是某个办公室的负责人。员工先在门卫处亮证件门卫给一张临时访客单员工拿着访客单到办公室办公室负责人打电话给门卫确认这张单有效才放行。访客单就是授权码电话确认就是后端换令牌。整个过程里办公室不需要员工把工牌押给他只需要确认门卫开过单。2.2 soular 认证中心的工作流程一次完整的登录流程按顺序拆开是七个环节。第一用户访问 Kanass 的受保护资源Kanass 后端发现本地会话不存在或已过期生成一个随机的 state 参数并缓存然后拼接授权 URL返回 302 重定向到 soular。第二浏览器带上重定向后的地址访问 soular 授权端点。这时候如果 soular 会话有效它会直接进入授权确认页或者直接跳转如果会话无效用户看到的是登录页。第三用户输入账号密码登录soular 校验通过后建立全局会话并生成一次性授权码 code。这个 code 会挂在回调 URL 的 query 参数上同时把 state 原样带回。第四浏览器跟随重定向访问 Kanass 的回调地址 /auth/callback。Kanass 后端先校验 state 是否与自己缓存的随机值一致防止跨站请求伪造。校验通过取出 code。第五Kanass 后端用 code 加上 client_secret 向 soular 的 token 端点发起 POST 请求换取 access_token、refresh_token 和 id_token。这一步必须放在后端因为 client_secret 不能暴露在浏览器端。第六Kanass 后端拿到 access_token 后调用 soular 的用户信息端点拿到用户的 opened、账号、部门等信息。如果启用了 OIDC也可以解析 id_token 里的标准声明但为了兼容后续版本我建议还是调一次用户信息接口毕竟身份字段可能扩展。第七Kanass 后端根据用户信息建立本地会话种下自己的会话 Cookie然后跳转到用户最初想访问的页面。之后再访问 Kanass 就不需要再走 soular直到本地会话过期。2.3 Kanass 业务系统如何信任 soular信任关系建立在整个对接的注册信息上。soular 给 Kanass 分配一个 client_id 和 client_secret它们相当于 Kanass 这个“客户端应用”的身份证和密钥。只有当请求里携带正确的 client_id并且能证明自己持有 client_secret 时soular 才认为这是合法的 Kanass 在请求令牌。回调地址 redirect_uri 也是信任链的一环。soular 在授权阶段就检查回调地址是否与注册时完全一致防止攻击者把授权码带到恶意域名上。实际踩坑最多的就是这里多一个斜杠、大小写不一致、端口没带全都会导致回调校验失败。令牌校验时Kanass 可以选择两种方式。一种是每次请求都拿着 access_token 调用 soular 的校验接口简单直接但每次都要打一次网络请求。另一种是信任 id_token 的签名Kanass 配置 soular 的公钥或 JWT 签名密钥本地解析令牌减少网络依赖。初期建议保守一点优先用接口校验稳定后再切换到本地验签。注意client_secret 一定要保存在 Kanass 后端的环境变量或配置中心不能写进前端代码也不能提交到代码仓库。一旦泄露相当于把统一登录的入口钥匙给了别人。3. 动手实现Kanass 与 soular 关联配置全流程理论说完开始配。这部分我按实际操作的顺序写包含环境准备、soular 侧注册、Kanass 侧配置、联调验证。每一步都配上参数和解释方便直接复现。3.1 准备环境与参数清单对接前先确认两边的环境。soular 需要有一套可访问的认证服务地址可以是测试环境的独立域名Kanass 需要有一个可以被浏览器访问的回调地址最好也配上独立域名不要用 IP 加乱端口因为回调地址对端口敏感。我这次在测试环境用的两组域名项目地址soular 认证中心http://soular.example.comKanass 业务系统http://kanass.example.comKanass 回调地址http://kanass.example.com/auth/callback域名不一定要公网可解析内网 DNS 能通就行但测试过程中要保证浏览器访问的域名和 soular 回调的域名完全匹配包括协议 http/https、域名、端口。还需要从 soular 管理后台确认几个端点地址。一般标准 OAuth2 服务都提供元数据文档比如http://soular.example.com/.well-known/openid-configuration里面能直接看到授权端点、令牌端点、用户信息端点。我把关键项列在这里参数示例值授权端点http://soular.example.com/oauth2/authorize令牌端点http://soular.example.com/oauth2/token用户信息端点http://soular.example.com/oauth2/userinfoclient_id在注册应用后生成client_secret在注册应用后生成scopeopenid profile custom_attrs3.2 soular 侧注册 Kanass 客户端在 soular 管理后台找到“应用管理”或“客户端管理”新建一个应用。应用名称填 Kanass类型选 Web 应用授权类型勾选 Authorization Code。回调地址填http://kanass.example.com/auth/callback。这里有两个细节。第一回调地址必须精确匹配。我当时因为填了http://kanass.example.com/auth/callback/末尾带斜杠导致回调校验始终失败浪费了大半天。第二soular 支持多个回调地址时通常以注册为准任何页面拼接都必须以这里登记的地址开头。保存后soular 会生成 client_id 和 client_secret。client_id 是公开的可以出现在前端跳转 URL 里client_secret 只在保存完成时展示一次一定要复制到后端配置里。如果需要授权 scope建议至少包含openid和profile如果有自定义部门、岗位字段再加一个custom_attrs。scope 越大意味着系统开放的数据越多不要把敏感字段全部授权出去。3.3 Kanass 侧配置认证源与回调地址Kanass 这边分前端和后端两部分。前端要做的只是生成一个跳转地址格式大致如下http://soular.example.com/oauth2/authorize?client_idYOUR_CLIENT_IDredirect_urihttp%3A%2F%2Fkanass.example.com%2Fauth%2Fcallbackresponse_typecodescopeopenid%20profile%20custom_attrsstateRANDOM_STRING这里state是前端或后端生成的一个随机字符串需要在跳转前存到临时会话或 Redis 里。回调时 soular 会把它原样带回来后端需要比对是否一致。没有 state等于给 CSRF 攻击留了一个门。后端需要提供一个回调接口我用的 Node.js 示例代码大致如下const crypto require(crypto); // GET /auth/callback async function handleCallback(req, res) { const { code, state } req.query; // 1. 校验 state防止 CSRF const savedState req.session.oauthState; if (!state || state ! savedState) { return res.status(400).send(state mismatch); } // 2. 用 code 换 token const tokenRes await fetch(http://soular.example.com/oauth2/token, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded }, body: new URLSearchParams({ grant_type: authorization_code, code, redirect_uri: http://kanass.example.com/auth/callback, client_id: process.env.SOULAR_CLIENT_ID, client_secret: process.env.SOULAR_CLIENT_SECRET }) }); const tokenData await tokenRes.json(); const accessToken tokenData.access_token; // 3. 拉取用户信息 const userRes await fetch(http://soular.example.com/oauth2/userinfo, { headers: { Authorization: Bearer ${accessToken} } }); const user await userRes.json(); // 4. 建立本地会话 req.session.user { id: user.sub, name: user.name, email: user.email, department: user.department }; res.redirect(/dashboard); }这段代码省去了 token 缓存和刷新逻辑但核心链路都在。建议在项目里单独封装一个OAuthClient把换 token、刷新 token、拉取用户信息都集中管理不要散落到业务代码里。3.4 登录跳转与令牌校验的联调要点配置完成后联调不是直接点登录按钮就行我建议按三步走。第一步验证跳转。在浏览器里直接请求 Kanass 的受保护地址观察 302 是否指向 soular。此时如果 soular 登录页正常显示说明前端跳转地址里 client_id、redirect_uri 基本没问题。如果出现 error 页面最常见的是unauthorized_client或redirect_uri_mismatch。第二步验证回调。在 soular 上登录后观察浏览器是否回到http://kanass.example.com/auth/callback?codexxxstatexxx。如果回不来检查 Kanass 服务有没有启动、回调地址是否正确、有没有被网关拦截。如果回来了但 Kanass 报 state 错误看是不是前后端跳转时 state 生成和校验用的不是同一个存储。第三步验证令牌。后端换 token 的时候可以用 curl 手动模拟一遍确认 soular 端点可用curl -X POST http://soular.example.com/oauth2/token \ -H Content-Type: application/x-www-form-urlencoded \ -d grant_typeauthorization_codecodeCODEredirect_urihttp%3A%2F%2Fkanass.example.com%2Fauth%2Fcallbackclient_idYOUR_CLIENT_IDclient_secretYOUR_CLIENT_SECRET如果返回了 access_token再拿这个 token 调 userinfocurl -H Authorization: Bearer ACCESS_TOKEN \ http://soular.example.com/oauth2/userinfo能拿到 JSON 形式的用户信息说明整个链路已经走通。剩下的工作就是把用户信息映射到 Kanass 本地用户表并建立会话。4. 登录流程中的关键细节与踩坑记录整体流程跑通以后我开始处理各种边界场景。这里记录的是实际遇到并解决的问题每一条都值得仔细看因为这些问题在文档里不太容易被提前警告。4.1 回调地址不一致导致的频繁跳转最典型的现象是用户登录成功后被跳回 KanassKanass 发现没有会话又跳到 soularsoular 发现已经登录又生成授权码又跳回 Kanass如此循环。从用户角度看页面一直在两个域名之间闪烁永远进不去。我排查时发现问题不在登录功能而在于 soular 回调 Kanass 的地址是http://kanass.example.com/auth/callback但 Kanass 代码里拼接的地址却是http://Kanass.example.com/auth/callback域名首字母大小写不一样。浏览器和服务器对域名大小写宽容程度不同soular 侧却把它当成了两个地址。最终统一改成小写再重启应用问题消失。另一个常见的循环原因是回调接口所在的网关把路径改写了。比如反代配置从/auth/callback转发到后端/callback但 soular 注册的回调地址网关上无法精确匹配。建议注册回调地址时直接确认这个地址能被外部访问并能到达后端服务不要依赖内部路径改写。4.2 令牌有效期与刷新策略access_token 默认有效期通常只有几十分钟到几个小时。如果 Kanass 只把 access_token 拿到手不处理 refresh_token用户本地会话过期后就得重新跳转 soular 登录。虽然这也是统一登录可接受的行为体验上却会显得“明明登录过还要再登录”尤其在内网环境里很烦。我的处理策略是双轨并行。本地会话有效期设置得比 access_token 短一旦本地会话过期Kanass 后端检查是否持有 refresh_token如果有就主动调用 soular 的 token 端点刷新 access_token再拉一次用户信息比对身份比对通过后重新建立本地会话。刷新接口的调用方式跟换 token 类似POST /oauth2/token grant_typerefresh_tokenrefresh_tokenREFRESH_TOKENclient_idCLIENT_IDclient_secretCLIENT_SECRET需要注意refresh_token 比 access_token 更敏感一旦泄露等同于长期访问凭证。Kanass 后端在存储 refresh_token 时至少要加密并且最好绑定客户端设备信息防止被拿到其他环境重放。4.3 跨域 Cookie 与自定义域名问题刚开始有同事提了一个方案让 soular 登录成功后把 Cookie 种到顶级域名下Kanass 直接读取。听起来省事但实际不可靠。企业内部可能不同系统分布在不同域名甚至不同一级域名共享 Cookie 的方案会直接把安全边界打开。正确的做法就是走授权码模式。Kanass 不依赖 soular 的 Cookie只在回调后获取 code 并换令牌。这样 Kanass 和 soular 即使完全不共享域名、不共享 Cookie也能建立信任。还有一个相关问题是跨域请求。如果 Kanass 前端需要直接调用 soular 的用户信息接口浏览器会触发 CORS。soular 侧需要配置允许的来源域名并确认是否需要携带凭证。能避免就避免这种设计因为前端调用意味着 access_token 可能被前端脚本访问XSS 风险更高。4.4 测试账号与生产环境隔离联调时最怕用同一个 soular 环境测试账号和生产账号混在一起。比如你在测试环境把某个账号的部门改了反过来影响生产数据或者测试回调地址里带了生产 client_id导致线上环境串号。我这边采用了两套 soular 应用配置。一套是测试应用client_id 后缀带-dev回调地址指向测试 Kanass。另一套是生产应用回调地址指向生产 Kanass。中间通过环境变量隔离部署时由 CI 自动替换。这样测试时可以随意注册临时账号不影响生产认证数据。另外日志里不要打印完整 code 和 access_token。我在排错时曾经把整个回调 URL 打到日志里结果里面带 code虽然只是内部环境但安全习惯还是要早点养成。建议只打印 token 的前几位和用户 ID。5. 常见问题速查与解决建议接入过程中肯定要面对各种报错。这里把我在 Kanass 对接 soular 时遇到的高频问题整理成一张速查表并讲讲快速定位问题的通用方法让下一次排错可以少走弯路。5.1 快速定位问题的方法遇到统一登录问题我习惯用“三步定位法”。第一步看浏览器网络面板。打开开发者工具切换到 Network 标签观察从访问 Kanass 到跳转 soular 再到返回 Kanass 的完整请求链。如果第一个请求是 302说明 Kanass 的登录拦截和跳转逻辑生效。如果请求一直停在一个地址打转大概率是回调匹配问题。第二步看 soular 侧日志。soular 会自动记录授权请求的来源、client_id、回调地址和错误码。通过错误码能区分是 client 配置问题还是用户凭证问题。我这边遇到过一个invalid_requestsoular 给出的原因是缺少 scope 参数很明显是前端跳转 URL 拼接不完整。第三步看 Kanass 侧日志。重点看回调接口有没有被调用、收到了什么参数、后端换 token 是否成功。如果 Kanass 日志里没有回调记录说明请求根本没到后端需要查网关路由如果收到 code 但换 token 失败需要检查 client_secret 和 redirect_uri。5.2 典型错误对照表下面这个表格是我踩坑过程中整理出来的基本可以覆盖第一轮对接的大部分报错。错误现象可能原因解决办法跳转 soular 时提示invalid_request授权 URL 缺参数或参数名写错对照文档检查 client_id、redirect_uri、response_type、scope提示unauthorized_clientclient_id 不存在或未启用在 soular 后台确认应用状态重新生成 client_id提示redirect_uri_mismatch回调地址与注册不一致统一协议、域名、端口、末尾斜杠两边严格一致回调后报state mismatchstate 存储和校验没对齐检查是存在 Cookie 还是 Redis确认取到的对象是否一致换 token 时提示invalid_grant授权码已使用、已过期或回调地址不匹配确认 code 只使用一次检查回调地址必要时重新登录拿到 token 后用户信息接口返回 401access_token 过期或未正确传 Bearer 头确认 token 是否过期刷新后再试登录成功但用户名总是 nullscope 没包含 profile 或自定义属性未授权在 soular 应用配置里追加 scope 并重新授权线上环境偶发无法登录服务器时钟与 soular 不同步同步 NTP 时间检查 JWT 签名允许的时间偏移这张表不能替代日志分析但它能在你两眼一抹黑的时候给出方向。处理任何报错先把错误码找到再回看配置比瞎试要有效得多。6. 一点实践经验Kanass 和 soular 的对接已经稳定运行了一段时间。回过头看技术本身不难难的是边界设计和配置一致性。下面这些经验是我个人的体会供后面再接手同类需求的同学参考。6.1 上线前要做的检查上线前请务必跑一遍完整流程清单每一项都确认通过之后再切流量。我习惯列四类检查未登录访问受保护页能否跳转、登录后能否正确回跳原页面、本地会话过期后能否无感刷新、主动退出后再次访问是否会被拦截。实际操作中最容易漏的是“旧会话”的处理。Kanass 老系统里有存量登录态上线统一登录后老会话要不要失效如果不处理可能出现用户从 soular 登出但 Kanass 老会话还能访问接口的情况。我这边采取的办法是给 Kanass 的会话增加版本号每次 soular 退出通知后把版本号加一旧会话全部失效。还有一个建议上线前在 soular 后台配置一个临时的维护白名单保证就算认证中心短暂不可用运维账号也能直接进入 Kanass 做紧急处理。这不算正规设计但关键时刻能救命。6.2 你可能会忽略的两个细节第一个是统一退出。soular 登录一次不代表点 Kanass 的退出按钮就能退出全局。需要调用 soular 的 logout 端点让它清掉认证中心的会话同时 Kanass 本地会话也要同步清理。否则用户以为退出了实际还是全局登录状态下次访问其他系统直接就通过了。第二个是账号关联。如果 Kanass 原本就有用户表不能简单拿着 soular 返回的用户 ID 直接当主键。建议在 Kanass 里维护一张映射表存 soular 的 sub 和 Kanass 本地用户 ID 的对应关系。在一些边界情况里比如用户被停用后重新开通映射表能避免产生多个重复账号。关于 Kanass 与 soular 的统一登录实践核心思路就是这些。最后再说句实在话这种对接工作不要想着一次就能完美按流程逐步验证多留意日志里的 error 参数基本都能顺利跑通。
阅读完成 · 觉得有帮助?