SpringCloud 前端不同域和后端同Redis 集成Sa-Token-SSO单点登录模块 账号统一认证中心 多端同步登录 登录状态共享 前后端分离

发布于:2025-06-24 ⋅ 阅读:(21) ⋅ 点赞:(0)

介绍

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

单点登录——就是为了解决这个问题而生!

简而言之,单点登录可以做到: 在多个互相信任的系统中,用户只需登录一次,就可以访问所有系统。

—Sa-Token

官方文档:https://sa-token.cc/doc.html#/sso/readme

在这里插入图片描述
模式一:采用共享 Cookie 来做到前端 Token 的共享,从而达到后端的 Session 会话共享。

模式二:采用 URL 重定向,以 ticket 码为授权中介,做到多个系统间的会话传播。

模式三:采用 Http 请求主动查询会话,做到 Client 端与 Server 端的会话同步。

搭建统一认证中心 SSO-Server 服务端

	<!-- SpringBoot -->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.14</version>
		<relativePath/>
	</parent>
	
	<!-- 定义 Sa-Token 版本号 -->
	<properties>
		<sa-token.version>1.44.0</sa-token.version>
	</properties>

	<dependencies>

		<!-- SpringBoot Web依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
		<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
		<dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
        
		<!-- Sa-Token 插件:整合SSO -->
		<dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-sso</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
        
		<!-- Sa-Token 插件:整合 RedisTemplate -->
		<dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-redis-template</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
		<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        
		<!-- 视图引擎(在前后端不分离模式下提供视图支持) -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<!-- Sa-Token 插件:整合 Forest 请求工具 (模式三需要通过 http 请求推送消息) -->
		<dependency>
			<groupId>cn.dev33</groupId>
			<artifactId>sa-token-forest</artifactId>
			<version>${sa-token.version}</version>
		</dependency>

		
	</dependencies>

认证中心配置文件

# 端口
# 端口
server:
    port: 9000

# Sa-Token 配置
sa-token:
    # 打印操作日志
    is-log: true
    # 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: "*"  #生产模式禁止使用
                allow-url: http://127.0.0.1:82/sso-login.html , http://127.0.0.1:81/sso-login.html
                secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
        
spring: 
    # Redis配置 (SSO模式一和模式二使用 Redis 来同步会话)
    redis:
        # Redis数据库索引(默认为0)
        database: 1
        # Redis服务器地址
        host: 127.0.0.1
        # Redis服务器连接端口
        port: 6379
        # Redis服务器连接密码(默认为空)
        password: 
        # 连接超时时间
        timeout: 10s
        lettuce:
            pool:
                # 连接池最大连接数
                max-active: 200
                # 连接池最大阻塞等待时间(使用负值表示没有限制)
                max-wait: -1ms
                # 连接池中的最大空闲连接
                max-idle: 10
                # 连接池中的最小空闲连接
                min-idle: 0
        
forest: 
    # 关闭 forest 请求日志打印
    log-enabled: false

           

跨域配置

处理统一认证中心的前端跨域问题

/**
 * [Sa-Token 权限认证] 配置类 (解决跨域问题)
  */
@Configuration
public class SaTokenConfigure {

    /**
     * CORS 跨域处理策略
     */
    @Bean
    public SaCorsHandleFunction corsHandle() {
        return (req, res, sto) -> {
            res.
                    // 允许指定域访问跨域资源
                            setHeader("Access-Control-Allow-Origin", "*")
                    // 允许所有请求方式
                    .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
                    // 有效时间
                    .setHeader("Access-Control-Max-Age", "3600")
                    // 允许的header参数
                    .setHeader("Access-Control-Allow-Headers", "*");

            // 如果是预检请求,则立即返回到前端
            SaRouter.match(SaHttpMethod.OPTIONS)
                    .free(r -> {})
                    .back();
        };
    }

}

在这里插入图片描述

异常处理

/**
 * 全局异常处理  
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

	// 全局异常拦截 
	@ExceptionHandler
	public SaResult handlerException(Exception e) {
		e.printStackTrace(); 
		return SaResult.error(e.getMessage());
	}
	
}

登录接口

在统一身份认证点击登录后会进入该方法

@RestController
public class SsoServerController {

	/**
	 * SSO-Server端:处理所有SSO相关请求 
	 * 		http://{host}:{port}/sso/auth			-- 单点登录授权地址
	 * 		http://{host}:{port}/sso/doLogin		-- 账号密码登录接口,接受参数:name、pwd
	 * 		http://{host}:{port}/sso/signout		-- 单点注销地址(isSlo=true时打开)
	 */
	@RequestMapping("/sso/*")
	public Object ssoRequest() {
		return SaSsoServerProcessor.instance.dister();
	}

	// 配置SSO相关参数
	@Autowired
	private void configSso(SaSsoServerTemplate ssoServerTemplate) {

		ssoServerTemplate.strategy.doLoginHandle = (name, pwd) -> {
			//可以在方法里通过 SaHolder.getRequest().getParam("xxx") 来获取前端提交的其它参数。

			// 此处仅做模拟登录,真实环境应该查询数据库进行登录
			if("sa".equals(name) && "123456".equals(pwd)) {
				String deviceId = SaHolder.getRequest().getParam("deviceId", SaFoxUtil.getRandomString(32));
				StpUtil.login(10001, new SaLoginParameter().setDeviceId(deviceId));
				return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
			}
			return SaResult.error("登录失败!");
		};

		// 添加消息处理器:userinfo (获取用户资料) (用于为 client 端开放拉取数据的接口)
		ssoServerTemplate.messageHolder.addHandle("userinfo", (ssoTemplate, message) -> {
			System.out.println("收到消息:" + message);

			// 自定义返回结果(模拟)
			return SaResult.ok()
					.set("id", message.get("loginId"))
					.set("name", "LinXiaoYu")
					.set("sex", "女")
					.set("age", 18);
		});

	}

}

以上就是统一认证中心的后端搭建


子系统登录认证

搭建好了统一认证中心的后端后,其他的系统就可以与他进行对接。

依赖

<!-- SpringBoot -->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.14</version>
		<relativePath/>
	</parent>
	
	<!-- 定义 Sa-Token 版本号 -->
	<properties>
		<sa-token.version>1.44.0</sa-token.version>
	</properties>

	<dependencies>

		<!-- SpringBoot依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
		<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
		<dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
        
		<!-- Sa-Token 插件:整合SSO -->
		<dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-sso</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
        
		<!-- Sa-Token 整合 RedisTemplate -->
		<dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-redis-template</artifactId>
            <version>${sa-token.version}</version>
        </dependency>
        
		<!-- 提供Redis连接池 -->
		<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>${sa-token.version}</version>
		</dependency>

		<!-- Sa-Token 插件:整合 Forest 请求工具 -->
		<dependency>
			<groupId>cn.dev33</groupId>
			<artifactId>sa-token-forest</artifactId>
			<version>${sa-token.version}</version>
		</dependency>

	</dependencies>

配置文件

# 端口
server:
    port: 9002

# sa-token配置 
sa-token:
    # 打印操作日志
    is-log: true

    # SSO-相关配置
    sso-client:
        # 应用标识
        client: sso-client2
        # SSO-Server 端主机地址
        server-url: http://sa-sso-server.com:9000
        
        # 在 sso-server 端前后端分离时需要单独配置 auth-url 参数(上面的不要注释,auth-url 配置项和 server-url 要同时存在)
        auth-url: http://127.0.0.1/sso-auth.html #统一认证中心的前端地址
        
        # API 接口调用秘钥 (单点注销时会用到)
        secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
    
    # 配置 Sa-Token 单独使用的Redis连接(此处需要和 SSO-Server 端连接同一个 Redis)
    # 注:使用 alone-redis 需要在 pom.xml 引入 sa-token-alone-redis 依赖
    alone-redis:
        # Redis数据库索引
        database: 1
        # Redis服务器地址
        host: 127.0.0.1
        # Redis服务器连接端口
        port: 6379
        # Redis服务器连接密码(默认为空)
        password: 
        # 连接超时时间
        timeout: 10s
        lettuce:
            pool:
                # 连接池最大连接数
                max-active: 200
                # 连接池最大阻塞等待时间(使用负值表示没有限制)
                max-wait: -1ms
                # 连接池中的最大空闲连接
                max-idle: 10
                # 连接池中的最小空闲连接
                min-idle: 0

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

子系统信息接口

如果不需要前后端分离架构下集成SSO,可删除此包下所有代码

/**
 * 前后台分离架构下集成SSO所需的代码 (SSO-Client端)
 * <p>(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)</p> *
 */
@RestController
public class H5Controller {

	// 判断当前是否登录
	@RequestMapping("/sso/isLogin")
	public Object isLogin() {
		return SaResult.data(StpUtil.isLogin()).set("loginId", StpUtil.getLoginIdDefaultNull());
	}
	
	// 返回SSO认证中心登录地址 
	@RequestMapping("/sso/getSsoAuthUrl")
	public SaResult getSsoAuthUrl(String clientLoginUrl) {
		String serverAuthUrl = SaSsoClientUtil.buildServerAuthUrl(clientLoginUrl, "");
		return SaResult.data(serverAuthUrl);
	}
	
	// 根据 ticket 进行登录
	@RequestMapping("/sso/doLoginByTicket")
	public SaResult doLoginByTicket(String ticket) {
		SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket);
		StpUtil.login(ctr.loginId, new SaLoginParameter()
				.setTimeout(ctr.remainTokenTimeout)
				.setDeviceId(ctr.deviceId)
		);
		return SaResult.data(StpUtil.getTokenValue());
	}

}

此外 跨域-异常处理 均与服务端的一致 !!!

子系统登录接口

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


	/*
	 * SSO-Client端:处理所有SSO相关请求 
	 * 		http://{host}:{port}/sso/login			-- Client 端登录地址
	 * 		http://{host}:{port}/sso/logout			-- Client 端注销地址(isSlo=true时打开)
	 * 		http://{host}:{port}/sso/pushC			-- Client 端接收消息推送地址
	 */
	@RequestMapping("/sso/*")
	public Object ssoRequest() {
		return SaSsoClientProcessor.instance.dister();
	}

	// 配置SSO相关参数
	@Autowired
	private void configSso(SaSsoClientTemplate ssoClientTemplate) {

	}

	// 当前应用独自注销 (不退出其它应用)
	@RequestMapping("/sso/logoutByAlone")
	public Object logoutByAlone() {
		StpUtil.logout();
		return SaSsoClientProcessor.instance._ssoLogoutBack(SaHolder.getRequest(), SaHolder.getResponse());
	}

	// 查询我的账号信息:sso-client 前端 -> sso-center 后端 -> sso-server 后端
	@RequestMapping("/sso/myInfo")
	public Object myInfo() {
		// 如果尚未登录
		if( ! StpUtil.isLogin()) {
			return "尚未登录,无法获取";
		}

		// 获取本地 loginId
		Object loginId = StpUtil.getLoginId();

		// 推送消息
		SaSsoMessage message = new SaSsoMessage();
		message.setType("userinfo");
		message.set("loginId", loginId);
		SaResult result = SaSsoClientUtil.pushMessageAsSaResult(message);

		// 返回给前端
		return result;
	}

}

前端请求顺序

官方流程图在这里插入图片描述
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到