单点登录原理、解决方案、基于satoken的实现

目录

设计思路

前端同域情况下:

搭建sso认证中心

引入依赖

开放处理认证的接口

配置信息

创建启动类

搭建业务系统

引入依赖

创建业务系统的controller

配置拦截器

配置信息

业务系统启动类

测试

前端不同域情况下:


设计思路

举个场景,假设我们的系统被切割为N个部分:商城、论坛、直播、社交…… 如果用户每访问一个模块都要登录一次,那么用户将会疯掉, 为了优化用户体验,我们急需一套机制将这N个系统的认证授权互通共享,让用户在一个系统登录之后,便可以访问其它所有系统。

我们需要搭建一个sso认证中心,就是不管那个系统需要登录认证都跳转到这个认证中心进行登录和认证。把SSO看作是一个单独的认证服务。不处理业务逻辑,只是做用户信息的管理以及授权给第三方应用。

单点登录的问题:

首先我们分析一下多个系统之间,为什么无法同步登录状态?实现单点登录就要解决这两个问题

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

前端同域情况下:

  1. 使用 共享Cookie 来解决 Token 共享问题。
  2. 使用 Redis 来解决 Session 共享问题。

所谓共享Cookie,就是主域名Cookie在二级域名下的共享,举个例子:写在父域名stp.com下的Cookie,在s1.stp.coms2.stp.com等子域名都是可以共享访问的。 所以后端在设置cookie的作用域的时候可以将其写入父级域名比如stp.com

我们在satoken框架配置文件中设置cookie的作用域为主域,同时后端服务器连接同一个redis

# 配置 Cookie 作用域 
sa-token.cookie.domain=stp.com

之后,比如在s1.stp.com 的子系统1登录之后返回一个在父域名stp.com下的Cookie 的token,在s2.stp.com 的子系统2去访问的时候带着这个token,就不需要再进行登录,从而实现了单点登录。

搭建sso认证中心

引入依赖

<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<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>

开放处理认证的接口

新建 SsoServerController,所有对系统资源的请求都先到这里的接口进行处理:

/**
 * Sa-Token-SSO Server端 Controller 
 */
@RestController
public class SsoServerController {


    /*
     * SSO-Server端:处理所有SSO相关请求 (下面的章节我们会详细列出开放的接口) 
     */
    @RequestMapping("/sso/*")
    public Object ssoRequest() {
        return SaSsoProcessor.instance.serverDister();
    }
    
    /**
     * 配置SSO相关参数 
     */
    @Autowired
    private void configSso(SaSsoConfig sso) {
        // 配置:未登录时返回的去登录的界面 
        sso.setNotLoginView(() -> {
            String msg = "当前会话在SSO-Server端尚未登录,请先访问"
                    + "<a href='/sso/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
                    + "进行登录之后,刷新页面开始授权";
            return msg;
        });
        
        // 配置:登录处理函数 
        sso.setDoLoginHandle((name, pwd) -> {
            // 此处仅做模拟登录,真实环境应该查询数据进行登录 
            if("sa".equals(name) && "123456".equals(pwd)) {
                StpUtil.login(10001);
                return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
            }
            return SaResult.error("登录失败!");
        });
    }
    
}

配置信息

# 端口
server.port=9000


################## Sa-Token 配置 ##################
# ------- SSO-模式一相关配置  (非模式一不需要配置) 
# 配置 Cookie 作用域为主域 
sa-token.cookie.domain=stp.com
    
################## Redis配置 (使用Redis来同步会话) ##################
# Redis数据库索引(默认为0)
spring.redis.database=1
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=


# 关闭 forest 请求日志打印
forest.log-enabled: false

创建启动类

@SpringBootApplication
public class SaSsoServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SaSsoServerApplication.class, args);
        System.out.println("\n------ Sa-Token-SSO 认证中心启动成功");
    }
}

至此我们可以通过http://localhost:9000/sso/auth进行登录,但真实项目中我们是不会直接从浏览器访问 /sso/auth 授权地址的,我们需要在 Client 端点击登录按钮重定向而来。

搭建业务系统

引入依赖

<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc -->
<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>

创建业务系统的controller

依然是用StpUtil.isLogin()来判断是否登录

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


    // SSO-Client端:首页 
    @RequestMapping("/")
    public String index() {
        String authUrl = SaSsoManager.getConfig().splicingAuthUrl();
        String solUrl = SaSsoManager.getConfig().splicingSloUrl();
        String str = "<h2>Sa-Token SSO-Client 应用端</h2>" + 
                    "<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" + 
                    "<p><a href=\"javascript:location.href='" + authUrl + "?mode=simple&redirect=' + encodeURIComponent(location.href);\">登录</a> " + 
                    "<a href=\"javascript:location.href='" + solUrl + "?back=' + encodeURIComponent(location.href);\">注销</a> </p>";
        return str;
    }
    
    // 全局异常拦截 
    @ExceptionHandler
    public SaResult handlerException(Exception e) {
        e.printStackTrace(); 
        return SaResult.error(e.getMessage());
    }
    
}

配置拦截器

我们还是用StpUtil.isLogin()或StpUtil.checkLogin()来拦截接口,若没有登录就会被拦截下来,跳转到sso认证中心进行登录,当在sso认证中心登录后StpUtil.isLogin()的结果就为true

// 登录校验 -- 用是否登录拦截所有路由,
// 在最下面的.excludePathPatterns中并排除登录等接口的拦截
SaRouter.match("/**", r -> StpUtil.checkLogin());

配置信息

# 端口
server.port=9001


######### Sa-Token 配置 #########


# SSO-Server端-单点登录授权地址 
sa-token.sso.auth-url=http://sso.stp.com:9000/sso/auth
# SSO-Server端-单点注销地址
sa-token.sso.slo-url=http://sso.stp.com:9000/sso/signout


# 配置 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

业务系统启动类

/**
 * SSO模式一,Client端 Demo 
 */
@SpringBootApplication
public class SaSso1ClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(SaSso1ClientApplication.class, args);
        System.out.println("\nSa-Token SSO模式一 Client端启动成功");
    }
}

测试

 

启动项目,依次访问三个应用端:

  • http://s1.stp.com:9001/
  • http://s2.stp.com:9001/
  • http://s3.stp.com:9001/

返回的结果都是(此时业务系统的controller里的StpUtil.isLogin()结果是false)

 

当我们点击登录按钮,会根据配置文件里的url转到我们之前搭建好的sso认证中心

 

当我们在sso认证中心登录后框架会自动将信息同步到同一个redis里面,这时候刷新原来的下面三个网址,业务系统的controller里的StpUtil.isLogin()结果都是true

  • http://s1.stp.com:9001/
  • http://s2.stp.com:9001/
  • http://s3.stp.com:9001/

 

前端不同域情况下:

  1. 使用 url重定向传播 来解决 Token 共享问题。
  2. 使用 Redis 来解决 Session 共享问题。

  • 用户进入 A 系统,没有登录凭证(ticket),A 系统给他跳到 SSO
  • SSO 没登录过,也就没有 sso 系统下没有凭证(注意这个和前面 A ticket 是两回事),输入账号密码登录
  • SSO 账号密码验证成功,通过接口返回做两件事:一是种下 sso 系统下凭证(记录用户在 SSO 登录状态);二是下发一个 ticket
  • 客户端拿到 ticket,保存起来,带着请求系统 A 接口
  • 系统 A 校验 ticket,成功后正常处理业务请求
  • 此时用户第一次进入系统 B,没有登录凭证(ticket),B 系统给他跳到 SSO
  • SSO 登录过,系统下有凭证,不用再次登录,只需要下发 ticket
  • 客户端拿到 ticket,保存起来,带着请求系统 B 接口

对浏览器来说,SSO 域下返回的数据要怎么存,才能在访问 A 的时候带上?浏览器对跨域有严格限制,cookie、localStorage 等方式都是有域限制的。

  • 在 SSO 域下,SSO 不是通过接口把 ticket 直接返回,而是通过一个带 code 的 URL 重定向到系统 A 的接口上,这个接口通常在 A 向 SSO 注册时约定
  • 浏览器被重定向到 A 域下,带着 code 访问了 A 的 callback 接口,callback 接口通过 code 换取 ticket
  • 这个 code 不同于 ticket,code 是一次性的,暴露在 URL 中,只为了传一下换 ticket,换完就失效
  • callback 接口拿到 ticket 后,在自己的域下 set cookie 成功
  • 在后续请求中,只需要把 cookie 中的 ticket 解析出来,去 SSO 验证就好
  • 访问 B 系统也是一样

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 中集成 Authing 单点登录,可以使用 satoken 框架来实现satoken 是一个轻量级的 Java Web 权限认证框架,它可以方便地集成第三方认证解决方案,如 Authing 单点登录。以下是实现步骤: 1. 首先,需要在 Authing 控制台中创建一个应用程序,并获取到应用程序的 App ID 和 App Secret。 2. 在 Spring Boot 项目中添加 satoken 和 Authing 的依赖: ``` <dependency> <groupId>cn.dev33.satoken</groupId> <artifactId>sa-token-starter</artifactId> <version>1.22.2-RELEASE</version> </dependency> <dependency> <groupId>com.authing</groupId> <artifactId>authing-java-sdk</artifactId> <version>3.0.0</version> </dependency> ``` 3. 在 Spring Boot 的配置文件中添加 Authing 的配置: ``` sa.authing.app-id=your_app_id sa.authing.app-secret=your_app_secret sa.authing.redirect-uri=http://localhost:8080/callback sa.authing.scope=openid profile email phone address ``` 4. 在 Spring Boot 中编写回调接口,用于接收 Authing 返回的用户信息: ```java @RestController public class AuthingCallbackController { @Autowired private AuthService authService; @GetMapping("/callback") public String callback(@RequestParam("code") String code) { AuthingUser user = authService.getUserByCode(code); // TODO: 处理用户信息 return "success"; } } ``` 5. 在 AuthService 中编写获取用户信息的方法: ```java @Service public class AuthService { @Autowired private SaOAuth2Authenticator saOAuth2Authenticator; public AuthingUser getUserByCode(String code) { SaOAuth2User user = saOAuth2Authenticator.doAuth("authing", code); // TODO: 处理用户信息 return new AuthingUser(); } } ``` 以上就是在 Spring Boot 中集成 Authing 单点登录的步骤。需要注意的是,Authing 单点登录需要使用 HTTPS 协议进行访问,因此需要在应用程序中配置 SSL 证书。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值