☰
统一登录与单点登录实战:网关与认证中心的搭建全解
2026/10/10 14:52:53 网站建设 项目流程

这段时间我一直在折腾一件事:把我们内部几个各自为战的业务系统,统一到一个登录入口底下。项目代号倒是很形象,sward 负责守门,soular 负责认人。说白了,sward 是一个网关层,soular 是一个身份认证中心,两者联动起来之后,用户只需要登录一次,就能带着身份凭证在多个系统之间跳转,这就是标题里说的统一登录。如果你正在给团队做单点登录,或者手里有几个 OAuth2/OIDC 相关组件不知道怎么串起来,这篇东西应该能省你不少探路的时间。

统一登录这件事听起来简单,真正落地时会碰一鼻子灰。比如回调地址对不上、token 丢失、跨域拿不到会话、刷新页面反复跳登录页……这些坑我基本都踩过一遍。所以这篇文章不打算只贴配置,我会把 sward 和 soular 各自该干什么、登录链路里每一步发生了什么、配置时哪些字段必须较真、出问题后怎么定位,全部摊开来讲。名字不是重点,重点是这两个角色怎么配合,用什么协议交互。只要把这套机制吃透,换成任何网关和认证中心都能照猫画虎。

1. 为什么是 sward + soular?——统一登录的整体设计思路

1.1 统一登录到底在解决什么问题

在没有统一登录之前,业务系统多起来之后是一种什么样的体验?我这边最典型的情况是:OA 一套账号、报表一套账号、内部工具又一套账号,每个系统都有自己的密码策略,用户要么记不住,要么干脆用同一个弱密码到处填。更麻烦的是,如果某个系统升级了登录逻辑,其他系统完全不知道,每次排查都要挨个问。

统一登录的核心就是把“你是谁”这件事从每个业务系统里抽出来,交给一个专门的认证中心。soular 在这里就是那个认证中心,它负责校验用户名密码、管理会话、签发身份凭证。而 sward 作为统一入口,负责把用户请求拦下来,判断你到底有没有登录过,再决定是放行还是跳去登录。这样一来,业务系统自己不需要保存密码,也不需要实现复杂的登录页,只需要认 sward 转发过来的身份信息就行。

这样做好处很明显:第一个是账号管理集中在 soular 一处,开通、禁用、改密都只操作一次;第二个是安全策略可以统一做,比如强制双因子、登录失败锁定、会话超时时间;第三个是用户体验提升,登录一次就能在所有接入系统里无缝切换。我后来复盘时觉得,统一登录的最大收益不是“少登几次”,而是把认证逻辑收拢到一个可审计、可管控的地方。

1.2 sward 和 soular 的职责边界

很多团队做统一登录失败,不是因为技术不会,而是因为职责没分清楚。sward 和 soular 的关系可以理解成“门卫”和“证件签发中心”:sward 在门口,负责看每个人手里有没有通行证;soular 在办公室,负责发通行证、登记个人信息、判断通行证是否有效。门卫不需要知道这个人平时喜欢点哪家外卖,证件签发中心也不关心门卫具体有几道门。

具体到技术实现,sward 只做三件事:路由、会话维持、身份透传。路由就是把不同路径的请求转发给对应的后端服务;会话维持就是管理用户在浏览器里的登录状态,通常体现为一个加密 cookie;身份透传就是把从 soular 拿到的用户信息以标准格式(比如 JWT)塞给下游服务。soular 也只做三件事:认证、授权、签发 token。认证是确认“密码对不对”;授权是确认“你能访问哪些系统”;签发 token 是生成一段双方都能信任的凭证。

这个边界划分非常重要。我曾经见过有人把 token 校验逻辑写在业务代码里,结果每个服务都要去 soular 查一次,认证中心压力大不说,业务系统还耦合了一堆安全细节。正确的做法是 sward 统一校验 token,业务系统只从请求头里读用户信息,完全不需要关心 token 怎么来的、怎么验的。这样后续如果要换认证中心,只要 sward 和 soular 的接口不变,业务系统一行代码都不用改。

1.3 登录流程的核心链路

把 sward 和 soular 串起来,完整的登录链路大致是这样:用户在浏览器里访问任意一个接入 sward 的业务地址,sward 发现这个用户没有携带有效的会话凭证,于是返回一个重定向,让浏览器跳到 soular 的登录页;soular 验证用户密码通过后,生成一个授权码,再把浏览器重定向回 sward 指定的回调地址;sward 收到授权码后,拿着它去 soular 的后端接口换 token;换到 token 后,sward 自己维护一条会话记录,同时把用户身份信息写入 cookie,最后带着用户回到最初想访问的那个页面。

这条链路走一遍,最多也就两三秒,但中间涉及三次重定向、两次服务端通信。第一次重定向是浏览器跳 soular,第二次重定向是 soular 跳回 sward,第三次是 sward 恢复用户原始访问路径。很多人配置失败,就是因为把这三步的 URL 写错了,或者漏了 HTTPS 导致 token 在跳转过程中被浏览器拦截。

这里有个关键点:sward 和 soular 之间换 token 的请求一定要在服务端完成,不能在前端用 ajax 直接发。因为换 token 需要用到 client_secret,这个值一旦暴露在浏览器端,就等于把认证中心的钥匙交给了所有人。我在下文的实操环节会专门演示这个服务端换 token 的写法。

2. 核心概念:Token、回调与会话同步

2.1 从一次登录看全局状态

统一登录和传统单系统登录最大的区别在于“状态”的存放位置。传统登录,状态存在应用服务器的 session 里;统一登录,状态分了两层:一层是 soular 里的全局会话,另一层是 sward 里的局部会话。全局会话代表“这个人在认证中心登录过”,局部会话代表“这个人在当前网关下的会话有效”。

一个很容易踩的坑是:全局会话过期了,局部会话还在,用户表面上看起来是登录状态,但一点击某个需要重新校验身份的功能就报错。反过来,局部会话过期了,全局会话还在,用户被踢回登录页,但重新登录时发现不用输入密码,直接跳回来了,体验上会觉得“系统抽风了”。我后来做了一个约定:soular 的全局会话有效期必须大于 sward 的局部会话有效期,这样即使局部会话失效,用户也能安静地重新走一遍静默登录,而不是被打断。

另外,多个接入系统之间的会话是独立的,sward 给每个系统分配的会话标识不能互相串。尤其是在浏览器多标签页的场景里,标签页 A 退出登录,标签页 B 下一跳就收到 401,这是合理的,因为局部会话已经销毁了。但很多用户会以为是 bug,建议在接入系统里把退出后的提示页面做得友好一点,告诉用户“你在其他标签页已经退出”。

2.2 soular 签发什么凭证

soular 在认证通过后,主要签发两类凭证:一类是授权码,另一类是 token。授权码是短命的,通常五分钟内有效,它不能直接证明身份,只能用来换 token;token 才是真正的身份凭证,一般会区分 access token 和 refresh token。access token 用于访问资源,有效期短;refresh token 用于在 access token 过期后重新换发,有效期长。

实际项目里我倾向于让 soular 签 JWT 格式的 access token。JWT 的好处是自带签名和用户信息,sward 拿到后不用回 soular 校验,本地验签就能知道用户是谁。验签需要用到 soular 公开出来的 JWKS 端点,sward 启动时会去拉取公钥,后续验签全部在本地完成,性能好很多。

不过 JWT 也有一个隐藏风险:因为它是自包含的,一旦签发就无法在过期前强制撤回。比如用户被禁用、改密码、踢下线,旧的 JWT 在有效期内仍然能通过验证。对于这个问题,我的处理方案是在 sward 的局部会话里额外维护一层黑名单,如果 soular 通过 webhook 通知某个用户状态变更,sward 就把对应的 jti 拉黑,这样既保留了 JWT 的性能优势,又能及时响应账号注销事件。

2.3 sward 如何校验与透传身份

sward 拿到 token 之后,并不是简单夹在请求里往下游传,而是要把 token 解析成结构化身份信息,再以约定的 header 格式传给后端服务。我常用的字段叫 X-User-Id 和 X-User-Name,后端服务只认这两个 header,不直接碰原始 token。这样做的意义是:业务系统不需要知道 token 的格式和签名算法,未来就算从 JWT 换成其他格式,业务系统也不用改动。

校验流程上,sward 需要做三步。第一步检查局部会话存在且有效,第二步检查 token 的签名和有效期,第三步检查权限范围。签名校验用 JWKS 公钥,有效期校验看 exp 字段,权限范围校验看 scope 或 audience 字段。很多配置问题出在第三步,比如 token 签发了但 audience 不是当前系统的,sward 应该拒绝放行,否则就是越权。

为了让调试方便,我习惯在 sward 的日志里把每一步的结果打出来,比如“auth redirect triggered, target system=report”“token exchanged, user=zhangsan, scope=openid profile email”。实际排查问题时,这些日志比什么抓包工具都直观。上线后这些日志要脱敏,不能把原始 token 打出来,否则日志泄露等于账号泄露。

3. 实战:从零配置一套统一的登录链路

3.1 环境准备与版本选型

开始配置前,我建议先把版本和部署方式定下来。soular 我选的是最新稳定版,支持 OIDC 协议,存储用 PostgreSQL;sward 作为网关层,我部署在它前面的 Nginx 后面,对外统一暴露 443 端口。soular 不直接对浏览器暴露,sward 和 soular 之间的通信走内网地址,避免认证中心被外部流量打爆。

环境清单大致如下:

组件版本/参数说明
sward2.6.1网关服务,对外端口 8443
soular4.3.0认证中心,内网端口 8080
数据库PostgreSQL 14存放用户、client、token 记录
前置代理Nginx 1.24HTTPS 终止,反向代理到 sward
接入系统Spring Boot 3.2示例业务服务,端口 9001

为什么 sward 不直接暴露 80 端口?因为统一登录对 HTTPS 有硬性要求。回调地址、cookie Secure 标志、token 传输过程都依赖 HTTPS,如果前面没有证书,浏览器会直接拦截或者把 cookie 丢弃。我踩过最惨的一次坑就是内网测试用 IP 加 HTTP,soular 回调地址填的却是 HTTP,结果换上 HTTPS 之后所有回调全部失效,浪费了半天排查。

在开始前,先把四个 URL 列在一张纸上:soular 的授权端点、soular 的 token 端点、sward 的登录回调地址、业务系统首页地址。后面所有配置都围绕这四个 URL 来。

3.2 soular 认证中心配置

在 soular 管理后台注册一个客户端,这个客户端代表 sward 网关。关键的配置项包括 client_id、client_secret、redirect_uri、grant_type、scope。redirect_uri 必须写 sward 的回调地址,比如 https://sward.example.com/callback,这里需要跟 sward 配置文件里完全一致,差一个斜杠都会导致验证失败。

我推荐用授权码模式(authorization_code),这是最安全的授权流程,因为密码只经过 soular,业务系统和 sward 都碰不到。scope 按实际需求开,最少是 openid profile,如果需要用户头像、邮箱再临时加。用最小化原则,不要什么权限都申请,soular 签发的 token 里信息越多,泄露后的风险越大。

配置示例:

# soular 客户端配置 clients: - client_id: sward-gateway client_secret: ${SWARD_CLIENT_SECRET} redirect_uris: - "https://sward.example.com/callback" grant_types: - authorization_code - refresh_token scopes: - openid - profile

配置完成后,记得做一次连通性测试:浏览器直接访问 soular 的授权端点,带上 client_id 和 redirect_uri,看能不能正常跳到登录页。如果这一步都不通,说明 soular 侧的基础配置有问题,先解决这个,再往下走。

3.3 sward 网关接入配置

sward 侧要配的内容稍微多一点:上游服务路由、认证规则、会话管理、身份透传 header。先把基础路由配上,再挂统一登录,这样方便定位问题。

一个最小化的 sward 配置长这样:

# sward 全局配置 server: port: 8443 ssl: enabled: true key-store: classpath:keystore.p12 key-store-password: ${SSL_PASSWORD} safety: login-enabled: true auth-server: "https://soular.intra.example.com" client-id: "sward-gateway" client-secret: ${SWARD_CLIENT_SECRET} redirect-uri: "https://sward.example.com/callback" jwks-url: "https://soular.intra.example.com/oidc/jwks" routes: - path: /report/** upstream: "http://10.0.1.20:9001" auth-required: true headers: X-User-Id: $.sub X-User-Name: $.name - path: /healthz/** upstream: "http://10.0.1.20:9001" auth-required: false

auth-required 字段很重要。像 /healthz 这类健康检查接口必须放行,否则监控系统探测时会收到 302 跳转,导致误报。而所有业务接口都建议收口到某个公共前缀下,比如 /report、/oa、/tool,每个前缀对应一个后端服务。这样 sward 的路由规则可以写得更简洁,也方便单独控制哪些服务需要登录。

启动 sward 后,访问一个受保护路径,如果能看到浏览器跳转到 soular 登录页,说明前半段链路已经通了。接下来要处理的是登录成功的回调。

3.4 第一个接入系统的改造

后端服务不需要自己写登录逻辑,但必须做一件事:读取 sward 转发过来的身份 header 并解析。这里有个前提,sward 必须保证所有进入上游服务的请求都已经验证过身份,业务服务只需要信任这些 header。但谨防有人绕过 sward 直接访问上游服务,所以业务服务所在网络要限制只允许 sward 的 IP 访问,最好落到防火墙白名单里。

以 Spring Boot 为例,我习惯写一个简单的拦截器,从请求头里提取用户信息:

@Component public class AuthHeaderInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String userId = request.getHeader("X-User-Id"); String userName = request.getHeader("X-User-Name"); if (userId == null || userId.isEmpty()) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } AuthContext.set(userId, userName); return true; } }

如果业务的容器框架是 Spring Security,可以直接在 SecurityConfig 里配置这个过滤器优先级高于其他认证过滤器。注意,不要让 token 直接进到业务系统,业务系统只需要拿到用户 ID 和关键属性即可,这样隔离层级更清晰。

接入完成后,测试一下完整流程:从浏览器访问 /report/index,应该跳到 soular,登录成功后自动跳回原页面。这个过程里如果发现跳转后没有带身份信息,优先排查 sward 的 token 换发是不是成功了,在 sward 的日志里找token exchanged关键字。

3.5 登录态刷新与退出登录

实战里还要处理两个细节:access token 过期了怎么办、用户主动退出怎么办。sward 和 soular 的联动方案是:sward 的局部会话有效期设置成 2 小时,soular 的全局会话有效期设置成 8 小时。当 sward 发现 access token 过期但 refresh token 还有效,就自动拿着 refresh token 去 soular 换新 token,不用打断用户。

实现上,sward 需要配置 refresh_token grant 的凭证:

# sward 令牌刷新配置 token-refresh: enabled: true refresh-token-path: /oauth2/refresh max-lifetime: 8h

退出登录这块有两个层次。只退 sward 局部会话,用户下次访问还会跳 soular 但能静默登录;退全局会话,才是真正的退出,所有接入系统一起失效。我的建议是默认让用户执行全局退出:先删 sward 的 cookie,再重定向到 soular 的 logout 端点,这样 soular 会清掉全局会话,同时通知其他接入系统。不要怕麻烦,统一登录的退出机制如果不彻底,才更麻烦。

4. 常见问题与排查技巧实录

4.1 回调地址比对失败

这是我在接入过程中遇到最多的报错,soular 页面上直接提示 “redirect_uri 不匹配”。原因几乎都是字符级不一致:有的多了一个斜杠,有的是 http 和 https 混用,有的是带上了默认端口,还有的是 IP 地址和域名混写。soular 校验回调地址是非常严格的,必须精确匹配注册值。

排查方法很直接:打开浏览器开发者工具,看登录前那次 302 重定向的 Location 参数,把 redirect_uri 参数和 soular 里注册的地址逐字对比。我自己的习惯是先复制地址到文本编辑器里对比,肉眼经常看不出区别,但编辑器能显示空格和转义符。这个步骤虽然笨,但最可靠。

提示:回调地址建议统一用 HTTPS,并且不要带路径参数,只写到域名加固定路径,比如 https://sward.example.com/callback。路径里尽量少一层转发,避免 Nginx 再改写导致回调地址和真实地址不一致。

4.2 Token 过期后页面刷新跳转循环

症状是用户正在填写一个很长的表单,刷新一下,页面又跳回登录页,登录完跳回来之后发现刚填的东西全丢了,再刷新又跳。造成这个循环的原因通常是 access token 有效期设得太短,而 sward 的局部会话又独立于 soular 全局会话,导致 sward 认为会话失效、soular 又认为已经登录过。

我的处理方案是三层配合:soular 的 access token 有效期稍微拉长到 30 分钟,refresh token 有效期 8 小时;sward 的局部会话有效期跟随 refresh token;页面前端保存一个用户正在编辑的 draft,后端接口在返回 401 时不要立即跳登录页,而是返回一个错误码,前端收到后静默调用 sward 的刷新接口。如果刷新接口返回成功,就原样重试刚才的请求;只有刷新失败才跳登录页。

还有一类循环是 sward 到自己 url 的登录回调被肝住了:登录成功后跳回原地址,但原地址也被判定为未登录,于是又去登录。这种一般是 sward 的 cookie 没有正确种到浏览器。排查项包括 cookie Secure 标志、Domain 配置、SameSite 属性,以及 sward 是否设置了Set-Cookie响应头。

4.3 前端跨域拿不到登录态

如果你的业务系统是前后端分离,前端静态资源放在 CDN,接口走 sward,这种架构很容易遇到跨域问题。浏览器发现页面域名和接口域名不一致,默认不会携带 cookie,导致用户明明登录过,前端却一直收到 401。

这种问题的解法是前后端约定好跨域凭证放行规则。前端在所有请求里加上credentials: include,后端 Nginx 和 sward 都要响应对应的 CORS 头,并且Access-Control-Allow-Origin不能写星号,必须写具体前端域名;Access-Control-Allow-Credentials设置为 true。这里要注意 preflight 请求通常不带 cookie,sward 需要允许 OPTIONS 请求通过,但不能把业务接口完全放行。

如果觉得 CORS 配置麻烦,我更推荐让前端走同域策略:静态资源也由 sward 或者 Nginx 代理,页面和接口都在同一个域名下,只是路径不同。这样可以彻底避开跨域 cookie 问题。很多内部系统不需要极致的 CDN 性能,同域反而是最省心的方案。

4.4 子服务时钟漂移导致验签失败

JWT 验签时有个隐形杀手:服务之间的系统时间不一致。如果 sward 和 soular 不在同一台机器,而且没有启用 NTP 时间同步,sward 验签时计算exp剩余时间可能出现偏差,导致一个刚签发不久、实际没过期的 token 被判定为过期。

这个坑不容易发现,因为收到的是 “token expired” 错误。排查办法是在 sward 和 soular 的两台机器上分别执行date命令看时间差,如果超过几十秒,就要考虑时间同步服务。我目前的部署规范里,所有走 auth 链路的主机都要配置 NTP,这是统一登录稳定运行的基本前提。

另外,验签时钟偏差最好留一点余量。sward 在算法上允许一个 leniency 窗口,比如接受 token 往后偏 30 秒内的过期时间,这个字段在 JWT 校验库里通常叫clock_skew。设为 30 到 60 秒比较合理,既能容忍轻微时钟漂移,又不会让过期 token 长时间存活。

4.5 问题速查表

症状可能原因处理建议
登录页跳转死循环cookie 没种上 / access token 有效期太短检查 Set-Cookie 属性,调整有效期层级
redirect_uri 不匹配注册地址与实际地址不一致逐个字符比对,统一使用 HTTPS 域名
登录后跳回 401前后端跨域没有携带 cookie配置 CORS credentials,或改为同域部署
日志提示 token expired时钟漂移 / token 实际过期同步 NTP 时钟,设置 clock_skew
退出登录后仍能访问只销毁局部会话,全局会话仍在重定向到 soular 全局 logout 端点
部分系统能登录部分不能audience 或 scope 不匹配检查 token 的 aud 字段是否包含当前系统

这套速查表并不是覆盖所有问题,但能覆盖 80% 的首次接入问题。剩下 20% 多半是网络隔离、防火墙、负载均衡会话保持这些基础设施问题,需要结合各自技术栈去排查。

最后分享一个小技巧:不要一上来就集成所有系统。先搭一个最简单的 demo,把 sward 和 soular 都装在本地,用两个测试页面把登录和退出流程跑通,再逐步接入真实业务系统。我后来反思,当初就是因为急着把生产系统接进来,导致第一次排障东一榔头西一棒槌。基础链路跑通之后再横向复制,后面每个系统接入几乎都是十分钟的事。做基础架构类改造,慢就是快,先把骨架打好,再往里填肉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询