Sa-Token 单点登录第三篇:跨域、同 Redis 下的 SSO 对接步骤

大家好,由于单篇博客长度有限,我将针对 SSO 单点登录 开启一个系列文章,此为第三篇,想完整的了解 SSO 技术的同学欢迎点开我的账号动态参考本系列历史文章。

Sa-Token 是一个轻量级 java 权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权 等一系列权限相关问题。
Gitee 开源地址:https://gitee.com/dromara/sa-token

凡是稍微上点规模的系统,统一认证中心都是绕不过去的槛。而单点登录 —— 便是我们搭建统一认证中心的关键。

如果我们的多个系统:部署在不同的域名之下,但是后端可以连接同一个Redis,那么便可以使用 SSO 模式二:[URL重定向传播会话] 的方式做到单点登录。

一、设计思路

首先我们再次复习一下,多个系统之间为什么无法同步登录状态?

  1. 前端的Token无法在多个系统下共享。
  2. 后端的Session无法在多个系统间共享。

关于第二点,我们已在之前的章节 "SSO模式一" 章节中阐述,使用 Alone独立Redis插件 做到权限缓存直连 SSO-Redis 数据中心,在此不再赘述。

而第一点,才是我们解决问题的关键所在,在跨域模式下,意味着 "共享Cookie方案" 的失效,我们必须采用一种新的方案来传递Token。

  1. 用户在 子系统 点击 [登录] 按钮。
  2. 用户跳转到子系统登录接口 /sso/login,并携带 back参数 记录初始页面URL。
    • 形如:http://{sso-client}/sso/login?back=xxx
  3. 子系统检测到此用户尚未登录,再次将其重定向至SSO认证中心,并携带redirect参数记录子系统的登录页URL。
    • 形如:http://{sso-server}/sso/auth?redirect=xxx?back=xxx
  4. 用户进入了 SSO认证中心 的登录页面,开始登录。
  5. 用户 输入账号密码 并 登录成功,SSO认证中心再次将用户重定向至子系统的登录接口/sso/login,并携带ticket码参数。
    • 形如:http://{sso-client}/sso/login?back=xxx&ticket=xxxxxxxxx
  6. 子系统根据 ticket码SSO-Redis 中获取账号id,并在子系统登录此账号会话。
  7. 子系统将用户再次重定向至最初始的 back 页面。

整个过程,除了第四步用户在SSO认证中心登录时会被打断,其余过程均是自动化的,当用户在另一个子系统再次点击[登录]按钮,由于此用户在SSO认证中心已有会话存在,
所以第四步也将自动化,也就是单点登录的最终目的 —— 一次登录,处处通行。

动态演示图:

SSO 模式二

下面我们按照步骤依次完成上述过程:

二、准备工作

首先修改hosts文件(C:\windows\system32\drivers\etc\hosts),添加以下IP映射,方便我们进行测试:

127.0.0.1 sa-sso-server.com
127.0.0.1 sa-sso-client1.com
127.0.0.1 sa-sso-client2.com
127.0.0.1 sa-sso-client3.com
  • 1
  • 2
  • 3
  • 4

三、搭建 Client 端项目

搭建示例在 Sa-Token 官方仓库的 /sa-token-demo/sa-token-demo-sso2-client/,如遇到难点可结合源码进行测试学习

在SSO模式一章节中我们打开了配置:

sa-token: 
    cookie: 
        # 配置 Cookie 作用域 
        domain: stp.com 
  • 1
  • 2
  • 3
  • 4

此为模式一专属配置,现在我们将其注释掉

3.2、创建 SSO-Client 端项目

创建一个 SpringBoot 项目 sa-token-demo-sso2-client,引入依赖:

<!-- Sa-Token 权限认证, 在线文档:https://sa-token.com -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.34.0</version>
</dependency>
<!-- Sa-Token 插件:整合SSO -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-sso</artifactId>
    <version>1.34.0</version>
</dependency>

<!-- Sa-Token 整合redis (使用jackson序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis-jackson</artifactId>
    <version>1.34.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!-- Sa-Token插件:权限缓存与业务缓存分离 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-alone-redis</artifactId>
    <version>1.34.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

Gradle 方式:

// Sa-Token 权限认证,在线文档:https://sa-token.com
implementation 'cn.dev33:sa-token-spring-boot-starter:1.34.0'

// Sa-Token 插件:整合SSO
implementation 'cn.dev33:sa-token-sso:1.34.0'

// Sa-Token 整合 Redis (使用 jackson 序列化方式)
implementation 'cn.dev33:sa-token-dao-redis-jackson:1.34.0'
implementation 'org.apache.commons:commons-pool2'

// Sa-Token插件:权限缓存与业务缓存分离
implementation 'cn.dev33:sa-token-alone-redis:1.34.0'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.3、创建 SSO-Client 端认证接口

同 SSO-Server 一样,Sa-Token 为 SSO-Client 端所需代码也提供了完整的封装,你只需提供一个访问入口,接入 Sa-Token 的方法即可。


/**
 * Sa-Token-SSO Client端 Controller 
 */
@RestController
public class SsoClientController {

    // 首页 
    @RequestMapping("/")
    public String index() {
        String str = "<h2>Sa-Token SSO-Client 应用端</h2>" + 
                    "<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" + 
                    "<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a> " + 
                    "<a href='/sso/logout?back=self'>注销</a></p>";
        return str;
    }

    /*
     * SSO-Client端:处理所有SSO相关请求 
     *      http://{host}:{port}/sso/login          -- Client端登录地址,接受参数:back=登录后的跳转地址 
     *      http://{host}:{port}/sso/logout         -- Client端单点注销地址(isSlo=true时打开),接受参数:back=注销后的跳转地址 
     *      http://{host}:{port}/sso/logoutCall     -- Client端单点注销回调地址(isSlo=true时打开),此接口为框架回调,开发者无需关心
     */
    @RequestMapping("/sso/*")
    public Object ssoRequest() {
        return SaSsoProcessor.instance.clientDister();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
3.4、配置SSO认证中心地址

你需要在 application.yml 配置如下信息:

# 端口
server:
    port: 9001

# sa-token配置 
sa-token: 
    # SSO-相关配置
    sso: 
        # SSO-Server端 统一认证地址 
        auth-url: http://sa-sso-server.com:9000/sso/auth
        # 是否打开单点注销接口
        is-slo: true

    # 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
    alone-redis: 
        # Redis数据库索引 (默认为0)
        database: 1
        # Redis服务器地址
        host: 127.0.0.1
        # Redis服务器连接端口
        port: 6379
        # Redis服务器连接密码(默认为空)
        password: 
        # 连接超时时间
        timeout: 10s
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

properties 风格:

# 端口
server.port=9001

######### Sa-Token 配置 #########
# SSO-Server端 统一认证地址 
sa-token.sso.auth-url=http://sa-sso-server.com:9000/sso/auth
# 是否打开单点注销接口
sa-token.sso.is-slo=true

# 配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
# Redis数据库索引
sa-token.alone-redis.database=1
# Redis服务器地址
sa-token.alone-redis.host=127.0.0.1
# Redis服务器连接端口
sa-token.alone-redis.port=6379
# Redis服务器连接密码(默认为空)
sa-token.alone-redis.password=
# 连接超时时间
sa-token.alone-redis.timeout=10s
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

注意点:sa-token.alone-redis 的配置需要和SSO-Server端连接同一个Redis(database也要一样)

3.5、写启动类

@SpringBootApplication
public class SaSso2ClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(SaSso2ClientApplication.class, args);
        System.out.println("\nSa-Token-SSO Client端启动成功");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

启动项目

四、测试访问

(1) 依次启动 SSO-ServerSSO-Client,然后从浏览器访问:http://sa-sso-client1.com:9001/

sso-client-index.png

(2) 首次打开,提示当前未登录,我们点击登录 按钮,页面会被重定向到登录中心

sso-server-auth.png

(3) SSO-Server提示我们在认证中心尚未登录,我们点击 doLogin登录按钮进行模拟登录

sso-server-dologin.png

(4) SSO-Server认证中心登录成功,我们回到刚才的页面刷新页面

sso-client-index-ok.png

(5) 页面被重定向至Client端首页,并提示登录成功,至此,Client1应用已单点登录成功!

(6) 我们再次访问Client2http://sa-sso-client2.com:9001/

sso-client2-index.png

(7) 提示未登录,我们点击登录按钮,会直接提示登录成功

sso-client2-index-ok.png

(8) 同样的方式,我们打开Client3,也可以直接登录成功:http://sa-sso-client3.com:9001/

sso-client3-index-ok.png

至此,测试完毕!

可以看出,除了在Client1端我们需要手动登录一次之外,在Client2端Client3端都是可以无需再次认证,直接登录成功的。

我们可以通过 F12控制台 Network 跟踪整个过程

sso-genzong

五、Ticket 劫持攻击

在上面的示例中,配置项 sa-token.sso.allow-url=* 意为配置所有允许的Client端授权地址,不在此配置项中的URL将无法单点登录成功。

为了方便测试,上述代码将其配置为*,但是,在生产环境中,此配置项绝对不能配置为 * ,否则会有被 Ticket 劫持的风险。

假设攻击者根据模仿我们的授权地址,巧妙的构造一个URL:

http://sa-sso-server.com:9000/sso/auth?redirect=https://www.baidu.com/

当不知情的小红被诱导访问了这个URL时,它将被重定向至百度首页

sso-ticket-jc

可以看到,代表着用户身份的 Ticket 码也显现到了URL之中,借此漏洞,攻击者完全可以构建一个URL将小红的 Ticket 码自动提交到攻击者自己的服务器,伪造小红身份登录网站

六、防范方法

造成T icket 劫持攻击的直接原因就是SSO-Server认证中心没有对 redirect地址 进行任何的限制,防范的方法也很简单,就是对redirect参数进行校验,如果其不在指定的URL列表中时,拒绝下放ticket

我们将其配置为一个具体的URL:

sa-token: 
    sso: 
        # 配置允许单点登录的 url 
        allow-url: http://sa-sso-client1.com:9001/sso/login
  • 1
  • 2
  • 3
  • 4
# 配置允许单点登录的 url 
sa-token.sso.allow-url=http://sa-sso-client1.com:9001/sso/login
  • 1
  • 2

再次访问上述链接:

sso-feifa-rf

域名没有通过校验,拒绝授权!

配置安全性参考表
配置方式 举例 安全性 建议
配置为* * 禁止在生产环境下使用
配置到域名 http://sa-sso-client1.com/* 不建议在生产环境下使用
配置到详细地址 http://sa-sso-client1.com:9001/sso/login 可以在生产环境下使用
疑问:为什么不直接回传 Token,而是先回传 Ticket,再用 Ticket 去查询对应的账号id?

Token 作为长时间有效的会话凭证,在任何时候都不应该直接暴露在 URL 之中(虽然 Token 直接的暴露本身不会造成安全漏洞,但会为很多漏洞提供可乘之机)

为了不让系统安全处于亚健康状态,Sa-Token-SSO 选择先回传 Ticket,再由 Ticket 获取账号id,且 Ticket 一次性用完即废,提高安全性。

七、下篇预告:跨 Redis 模式下的解决方案

以上流程解决了跨域模式下的单点登录,但是后端仍然采用了共享 Redis 来同步会话,如果我们的架构设计中Client端与Server端无法共享Redis,又该怎么完成单点登录?

这就是下篇博客要介绍的 SSO 模式三:跨域、跨 Redis 下的 SSO 对接步骤。


参考资料

← Sa-Token 单点登录第一篇:搭建 SSO-Server 统一认证中心 Sa-Token 单点登录第二篇:同域、同 Redis 下的 SSO 对接步骤 →

当前博客已开放版权,所有人均可免费转载,无需联系 Sa-Token 团队获取授权。只需要在转载时保留底部 Sa-Token 官网链接 + 开源仓库链接即可。

Copyright ©2026 Sa-Token java 权限认证 | sa-token.com | 鲁ICP备18046274号-5 | 鲁公网安备37011202002956号