Sa-Token SSO 平台中心跳转模式:把 sso-server 建成统一门户,一次登录免登进入全部子系统
【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token
本篇围绕 Sa-Token SSO 的“平台中心跳转模式”展开:当 sso-server 不再只是认证中心,而是承担企业门户首页的角色时,如何做到“平台中心一处登录,所有子系统无障碍通行”。文中给出完整的home-route配置、平台首页 Controller 代码,并结合 sa-token-sso 模块源码解析跳转链接背后的 ticket 免登录机制,读完你可以独立搭建一套“登录中心 + 子系统导航首页”的 SSO 门户。
一、场景说明:sso-server 即平台中心
在标准 SSO 场景下,sso-server 通常只做一件事:提供登录页并在认证成功后重定向回发起请求的子系统。但在很多多业务线、多子系统并存的企业环境中,运营方希望把 sso-server 搭建成一个平台中心(统一门户):
- 用户访问平台地址(如
http://sa-sso-server.com:9000),未登录则先进入/sso/auth登录页; - 登录成功后回到平台首页,首页上罗列各子系统的进入链接;
- 用户点击某个子系统链接,浏览器直接跳转到该子系统,并且跳转的同时自动完成子系统登录,无需再次输入账号密码。
难点不在“加超链接”(这毫无技术含量),而在于第 3 步:如何让一次普通的链接跳转触发子系统的自动登录。直接跳转肯定做不到,需要对跳转链接做协议化改造。
二、核心原理:对跳转链接做一次“SSO 协议化”改造
假设某子系统的地址是:
http://sa-sso-client1.com:9003/将其改造为平台首页上可点击的链接:
/sso/auth?client=sso-client3&redirect=http://sa-sso-client1.com:9003/sso/login?back=http://sa-sso-client1.com:9003/通用格式形如:
/sso/auth?client={client标识}&redirect=${子系统首页}/sso/login?back=${子系统首页}这个格式的含义需要拆开理解:
- 链接首先指向 sso-server 的
/sso/auth授权入口; redirect参数并不直接指向子系统首页,而是指向子系统的 SSO 登录中转页/sso/login;back参数(内嵌在 redirect 里)才是子系统真正的落地地址。
也就是说,用户点击链接后的完整路径是:平台首页 → sso-server/sso/auth→ 子系统/sso/login?ticket=xxx&back=xxx→ 子系统首页。真正完成“免登录”动作的是子系统/sso/login中转页,这一点下文的源码分析会详细展开。
三、第一步:在 sso-server 配置home-route
home-route解决的是登录完成后的“落点”问题:用户在/sso/auth登录页完成认证、但请求中没有指定redirect参数(即用户不是从某个子系统跳来的,而是直接访问认证中心)时,认证中心应该把用户送到哪里。此时就重定向到home-route配置的路由上,也就是平台首页。
yaml 风格配置:
# Sa-Token 配置 sa-token: # SSO-Server 配置 sso-server: # 主页路由:在 /sso/auth 登录页不指定 redirect 参数时,默认跳转的地址 home-route: /homeproperties 风格配置:
# 主页路由:在 /sso/auth 登录页不指定 redirect 参数时,默认跳转的地址 sa-token.sso-server.home-route: /home该配置项映射到 SaSsoServerConfig 配置类的homeRoute字段(无默认值,需显式配置)。其消费逻辑位于 SaSsoServerProcessor.ssoAuth():
// 若 redirect 参数为空,说明用户并不是从 client 重定向来的,而是直接访问的 http://sso-server.com/sso/auth 地址 // 此时需要跳转到配置的 homeRoute 路由上, // 若 homeRoute 也为空,则没有明确的跳转地址了,需要抛出异常 if(SaFoxUtil.isEmpty(redirect)) { if(SaFoxUtil.isEmpty(cfg.getHomeRoute())) { throw new SaSsoException("未指定 redirect 参数,也未配置 homeRoute 路由,无法完成重定向操作").setCode(SaSsoErrorCode.CODE_30014); } return cfg.getHomeRoute(); }由此可以确认两个关键行为(有 SaSsoServerProcessorTest 单元测试印证):
- 已登录、无
redirect、未配置homeRoute:抛出 SSO 异常,错误码30014(定义见 SaSsoErrorCode); - 已登录、无
redirect、配置了homeRoute:直接 302 重定向到homeRoute地址,平台首页模式正是依赖这条分支。
此外ssoAuth()中还有一个与平台模式相关的细节:redirect参数会先经过SaFoxUtil.decoderUrlIfEncoded()做一次保险解码(L120-L121),因为各 web 容器对参数值的解码行为不统一,这一层防御可以避免redirect中嵌套的?、&等字符在传递过程中被破坏——这对本模式非常重要,因为改造后的链接本身就是一条“参数里套参数”的深层 URL。
四、第二步:在 sso-server 编写平台中心首页 HomeController
登录完成后的落点/home需要有真实的路由承载。仓库中的演示工程 HomeController 即为该模式的完整参考实现:
/** * SSO 平台中心模式示例,跳连接进入子系统 */ @RestController public class HomeController { // 平台化首页 @RequestMapping({"/", "/home"}) public Object index() { // 如果未登录,则先去登录 if(!StpUtil.isLogin()) { return SaHolder.getResponse().redirect("/sso/auth"); } // 拼接各个子系统的地址,格式形如:/sso/auth?client=xxx&redirect=${子系统首页}/sso/login?back=${子系统首页} String link1 = "/sso/auth?client=sso-client3&redirect=http://sa-sso-client1.com:9003/sso/login?back=http://sa-sso-client1.com:9003/"; String link2 = "/sso/auth?client=sso-client3&redirect=http://sa-sso-client2.com:9003/sso/login?back=http://sa-sso-client2.com:9003/"; String link3 = "/sso/auth?client=sso-client3&redirect=http://sa-sso-client3.com:9003/sso/login?back=http://sa-sso-client3.com:9003/"; // 组织网页结构返回到前端 String title = "<h2>SSO 平台首页 (平台中心模式)</h2>"; String client1 = "<p><a href='" + link1 + "' target='_blank'> 进入Client1系统 </a></p>"; String client2 = "<p><a href='" + link2 + "' target='_blank'> 进入Client2系统 </a></p>"; String client3 = "<p><a href='" + link3 + "' target='_blank'> 进入Client3系统 </a></p>"; return title + client1 + client2 + client3; } }实现要点逐条说明:
- 未登录拦截:
/与/home两个路径均要求登录态,未登录时通过SaHolder.getResponse().redirect("/sso/auth")跳回认证页。由于认证页此时不带redirect参数,登录完成后会经由home-route回到本页面,形成“登录 → 首页”闭环; - 链接拼接规则:每条子系统链接都遵循
/sso/auth?client={client标识}&redirect=${子系统首页}/sso/login?back=${子系统首页}格式。演示工程为了简化,三条链接统一使用client=sso-client3(模式三,跨域跨 Redis),且该 client 的allow-url配置为*,因此三个不同域名的回调地址都能通过校验; - 返回结构:演示中直接拼接 HTML 字符串返回;实际项目可替换为 Thymeleaf / Freemarker / 前端页面模板,把 link1~link3 作为动态数据渲染,甚至按用户角色过滤出有权限的子系统入口。
演示工程的配套 application.yml 给出了完整的 Server 端配置参考(节选):
server: port: 9000 sa-token: # SSO-Server 配置 sso-server: # Ticket有效期 (单位: 秒),默认五分钟 ticket-timeout: 300 # 主页路由:在 /sso/auth 登录页不指定 redirect 参数时,默认跳转的地址 home-route: /home # 是否启用匿名 client (开启匿名 client 后,允许客户端接入时不提交 client 参数) allow-anon-client: true # 所有允许的授权回调地址 (匿名 client 使用) allow-url: "*" # API 接口调用秘钥 (全局默认 + 匿名 client 使用) secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor # 应用列表:配置接入的应用信息 clients: # 应用 sso-client1:采用模式一对接 (同域、同Redis) sso-client1: client: sso-client1 allow-url: "*" # 应用 sso-client2:采用模式二对接 (跨域、同Redis) sso-client2: client: sso-client2 allow-url: "*" secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor # 应用 sso-client3:采用模式三对接 (跨域、跨Redis) sso-client3: client: sso-client3 allow-url: "*" is-push: true push-url: http://sa-sso-client1.com:9003/sso/pushC secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor生产环境中需要注意:allow-url是对redirect回调地址的白名单校验,子系统回调地址必须落在对应 client 的 allow-url 范围内,否则认证中心拒绝下发 ticket;演示用*是图方便,实际部署应收敛为具体的子系统域名。
五、跳转链路纵深解析:ticket 如何在一次跳转中完成免登录
平台首页的链接只是“触发器”,真正把用户免登录送进子系统的是 Sa-Token SSO 的 ticket 机制。从源码结构看,完整调用链如下。
第 1 跳:用户点击链接,到达 sso-server/sso/auth。用户此刻已持有认证中心登录态(平台首页已保证),ssoAuth()走“已登录”分支。由于链接中未带mode参数,取默认值SaSsoConsts.MODE_TICKET(见 SaSsoServerProcessor.java),即走 ticket 模式:
// 方式2:带着 ticket 参数重定向回Client端 (mode=ticket,一般是模式二、三) String _redirectUrl = ssoServerTemplate.buildRedirectUrl(client, redirect, stpLogic.getLoginId(), stpLogic.getTokenValue()); // 构建成功,说明 redirect 地址合法,此时需要更新一下当前 token 有效期 if(cfg.getAutoRenewTimeout()) { stpLogic.renewTimeout(stpLogic.getConfigOrGlobal().getTimeout()); } return _redirectUrl;即认证中心为当前登录用户生成一次性 ticket,拼到redirect地址上(形如http://sa-sso-client1.com:9003/sso/login?back=...&ticket=...)后 302 跳走。buildRedirectUrl内部会执行allow-url白名单校验与 client 校验,非法回调地址在此被拦截。配置项autoRenewTimeout(默认false)控制是否在每次下发 ticket 时按全局timeout自动续期认证中心 token,门户中心这种“高频跳转”场景可酌情开启,避免用户频繁访问各子系统期间 token 过期。
第 2 跳:到达子系统/sso/login中转页。SaSsoClientProcessor.ssoLogin() 根据是否携带ticket参数分两条路:
public Object ssoLogin() { String ticket = req.getParam(paramName.ticket); /* * 此时有两种情况: * 情况1:ticket 无值,说明此请求是 sso-client 端访问,需要重定向至 sso-server 认证中心 * 情况2:ticket 有值,说明此请求从 sso-server 认证中心重定向而来,需要根据 ticket 进行登录 */ if(ticket == null) { return _goServerAuth(); } else { return _loginByTicket(); } }本模式中用户带着 ticket 到达,进入_loginByTicket()(L228-L254):校验 ticket 换出loginId、deviceId、剩余 token 有效期等信息,调用stpLogic.login(...)在子系统本地建立登录态,最后 302 到back参数指定的子系统首页。
ticket 校验有两种实现,由 client 端is-http配置决定(见 SaSsoClientProcessor.checkTicket()):
is-http=true(模式三,跨域跨 Redis):子系统通过 HTTP 请求(带签名)向认证中心校验 ticket;is-http=false(模式二,跨域同 Redis):子系统直连 Redis 校验并删除 ticket,省一次网络往返。
ticket 是一次性凭证:认证成功后即被删除(checkTicketParamAndDelete),有效期由ticket-timeout控制(默认 300 秒,演示配置同)。这保证了即便跳转链接被截取,也无法重复利用来登录。
一个值得注意的短路逻辑:若子系统发现当前其实已登录(_goServerAuth()中 L209-L211 的stpLogic.isLogin()判断),则不再访问认证中心,直接重定向回back。也就是说,平台首页链接对“已登录该子系统的用户”是一次零成本的普通跳转,只有首次进入或会话过期时才会走完整 ticket 链路。
六、验证与测试
集成验证:启动 sso-server 演示工程后访问http://sa-sso-server.com:9000(演示环境域名,本地可用localhost:9000)。首次访问因未登录被重定向到/sso/auth登录页;登录成功后,由于请求中无redirect参数,认证中心按home-route: /home重定向到平台首页;随后依次点击三个子系统链接,浏览器在跳转的同时自动登录对应子系统。
单元测试印证:SaSsoServerProcessorTest 对home-route相关分支做了精确覆盖:
ssoAuth_loggedInEmptyRedirectNoHome_throws30014:已登录、无 redirect、无 homeRoute → 断言抛出30014;ssoAuth_loggedInEmptyRedirectHasHome_redirectsHome:已登录、无 redirect、配置homeRoute→ 断言重定向目标等于 homeRoute;ssoAuth_modeTicket_containsTicketAndRedirect:默认 ticket 模式下,redirect 上会拼出ticket=参数。
七、落地时的注意事项
home-route未配置且无 redirect 会报 30014:平台中心模式必须配置该项,否则用户直接访问认证中心登录后无处可去;- redirect 是“参数套参数”的深层 URL:认证中心已内置
decoderUrlIfEncoded防御性解码(SaSsoServerProcessor.java),但前端自行渲染链接时仍建议确保redirect值按 URL 规范传递,避免二次编码问题; - allow-url 白名单必须覆盖各子系统回调地址:演示中三条链接统一使用
client=sso-client3且allow-url: *,生产环境建议每个子系统使用独立 client 标识,并将 allow-url 收敛到各自域名; - ticket 一次性 + 时效性:不要试图缓存/复用跳转链接中的 ticket;跨域跨 Redis 的子系统请保持
secret-key与认证中心配置一致,is-check-sign生产环境务必为 true(配置项默认值见 SaSsoServerConfig); - 首页权限裁剪:
HomeController可按登录用户的角色/权限动态生成入口列表,把“平台中心”从统一导航页升级为按角色呈现的应用市场。
综上,Sa-Token 的平台中心跳转模式并不引入额外协议:它复用了 SSO 既有的/sso/auth授权入口与 ticket 免登录机制,仅靠home-route配置 + 首页链接的格式约定,就把“认证中心”升级为“认证中心 + 统一门户”,实现一处登录、全系统免登通行。
【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考