news 2026/9/13 8:55:05

Sa-Token SSO 平台中心跳转模式:把 sso-server 建成统一门户,一次登录免登进入全部子系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sa-Token SSO 平台中心跳转模式:把 sso-server 建成统一门户,一次登录免登进入全部子系统

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 搭建成一个平台中心(统一门户):

  1. 用户访问平台地址(如http://sa-sso-server.com:9000),未登录则先进入/sso/auth登录页;
  2. 登录成功后回到平台首页,首页上罗列各子系统的进入链接;
  3. 用户点击某个子系统链接,浏览器直接跳转到该子系统,并且跳转的同时自动完成子系统登录,无需再次输入账号密码。

难点不在“加超链接”(这毫无技术含量),而在于第 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: /home

properties 风格配置:

# 主页路由:在 /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; } }

实现要点逐条说明:

  1. 未登录拦截//home两个路径均要求登录态,未登录时通过SaHolder.getResponse().redirect("/sso/auth")跳回认证页。由于认证页此时不带redirect参数,登录完成后会经由home-route回到本页面,形成“登录 → 首页”闭环;
  2. 链接拼接规则:每条子系统链接都遵循/sso/auth?client={client标识}&redirect=${子系统首页}/sso/login?back=${子系统首页}格式。演示工程为了简化,三条链接统一使用client=sso-client3(模式三,跨域跨 Redis),且该 client 的allow-url配置为*,因此三个不同域名的回调地址都能通过校验;
  3. 返回结构:演示中直接拼接 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 换出loginIddeviceId、剩余 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=参数。

七、落地时的注意事项

  1. home-route未配置且无 redirect 会报 30014:平台中心模式必须配置该项,否则用户直接访问认证中心登录后无处可去;
  2. redirect 是“参数套参数”的深层 URL:认证中心已内置decoderUrlIfEncoded防御性解码(SaSsoServerProcessor.java),但前端自行渲染链接时仍建议确保redirect值按 URL 规范传递,避免二次编码问题;
  3. allow-url 白名单必须覆盖各子系统回调地址:演示中三条链接统一使用client=sso-client3allow-url: *,生产环境建议每个子系统使用独立 client 标识,并将 allow-url 收敛到各自域名;
  4. ticket 一次性 + 时效性:不要试图缓存/复用跳转链接中的 ticket;跨域跨 Redis 的子系统请保持secret-key与认证中心配置一致,is-check-sign生产环境务必为 true(配置项默认值见 SaSsoServerConfig);
  5. 首页权限裁剪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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 8:54:56

amis InputVerificationCode 验证码输入组件详解:属性、事件与源码实现

amis InputVerificationCode 验证码输入组件详解&#xff1a;属性、事件与源码实现 【免费下载链接】amis 前端低代码框架&#xff0c;通过 JSON 配置就能生成各种页面。 项目地址: https://gitcode.com/GitHub_Trending/am/amis 本篇技术文章以 amis 低代码框架的验证码…

作者头像 李华
网站建设 2026/9/13 8:53:20

二阶锥松弛在微电网最优潮流计算中的应用与Matlab实现

1. 项目背景与核心价值微电网作为分布式电源接入配电网的重要载体&#xff0c;其灵活性调节能力直接影响着配电网运行的经济性和安全性。传统配电网最优潮流&#xff08;OPF&#xff09;计算在考虑分布式电源时&#xff0c;常因非凸非线性特性导致求解困难。二阶锥松弛&#xf…

作者头像 李华
网站建设 2026/9/13 8:49:21

三道经典测试题,快速分辨GPT-4与GPT-3.5套壳接口

告诉你个尴尬事。前阵子有个朋友找我&#xff0c;说他们团队买了一家第三方AI接口&#xff0c;宣传清清楚楚写着“GPT-4”&#xff0c;结果做出来的产品在复杂逻辑问答上一塌糊涂&#xff0c;用户投诉不断。我让他把接口的返回参数亮出来&#xff0c;结果model字段里赫然写着“…

作者头像 李华
网站建设 2026/9/13 8:48:47

GitHub到Gitea仓库迁移实战:含批量脚本与踩坑避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华