当前位置: 首页 > article >正文

Sa-Token核心功能解剖二( Session会话、 持久层Redis扩展 、全局侦听器 、全局过滤器、多账号体系认证、单点登录)

文章目录

    • 概要
    • 功能结构图
      • 5.Session会话
      • 6.持久层扩展
      • 7.全局侦听器
      • 8.全局过滤器
      • 9.多账号体系认证
      • 10.单点登录

概要

Sa-Token核心功能解剖(二),主要有:

  • Session会话 —— 全端共享Session,单端独享Session,自定义Session,方便的存取值。
  • 持久层扩展 —— 可集成 Redis,重启数据不丢失。
  • 全局侦听器 —— 在用户登陆、注销、被踢下线等关键性操作时进行一些AOP操作。
  • 全局过滤器 —— 方便的处理跨域,全局设置安全响应头等操作。
  • 多账号体系认证 —— 一个系统多套账号分开鉴权(比如商城的 User 表和 Admin 表)
  • 单点登录 —— 内置三种单点登录模式:同域、跨域、同Redis、跨Redis、前后端分离等架构都可以搞定。

功能结构图

5.Session会话

  • 全端共享Session(Account-Session): PC 和 APP 登录的账号id一致,它们对应的都是同一个Session,两端可以非常轻松的同步数据。
  • 单端独享Session(Token-Session):同一账号在不同设备登录,产生单端独享的token和Token-Session,实现不同端会话Session的分隔、独享。
  • 自定义Session( Custom-Session),方便的存取值。

Session 是会话中专业的数据缓存组件,通过 Session 我们可以很方便的缓存一些高频读写数据(如用户信息登录关联的User信息),提高程序性能。
Sa-Token 中,Session 分为三种,分别是:

  • Account-Session: 每个 账号id 分配的 Session,调用StpUtil.login(id)登录会话时才会产生。
  • Token-Session: 每个 token 分配的 Session
  • Custom-Session: 以一个特定的值作为SessionId,来分配的Session。

Session会话模型详解

Sa-Token 常量类SaTokenConsts:

public class SaTokenConsts {
	//SaSession 的类型: Account-Session
	public static final String SESSION_TYPE__ACCOUNT = "Account-Session";
	//SaSession 的类型: Token-Session
	public static final String SESSION_TYPE__TOKEN = "Token-Session";
	//SaSession 的类型: Custom-Session
	public static final String SESSION_TYPE__CUSTOM = "Custom-Session";
......
}

1)全端共享Session(Account-Session)

	//会话登录
	//id 账号id,建议的类型:(long | int | String)
	public void login(Object id) {
		login(id, createSaLoginParameter());
	}
	--->
	
	/**
	 * 创建指定账号 id 的登录会话数据
	 * @param id 账号id,建议的类型:(long | int | String)
	 * @param loginParameter 此次登录的参数Model 
	 * @return 返回会话令牌 
	 */
	public String createLoginSession(Object id, 
	SaLoginParameter loginParameter) {
		.....
		// 3、获取此账号的 Account-Session , 续期
		SaSession session = getSessionByLoginId(id, true, 
		loginParameter.getTimeout());
		.....
       return session;
    }
    --->

    //获取指定账号 id 的 Account-Session, 如果该 SaSession 
    //尚未创建,isCreate=是否新建并返回
    public SaSession getSessionByLoginId(Object loginId, 
    boolean isCreate, Long timeout) {
		if(SaFoxUtil.isEmpty(loginId)) {
			throw new SaTokenException("Account-Session 获取失败:
			loginId 不能为空");
		}
		return getSessionBySessionId(
		splicingKeySession(loginId), isCreate, timeout, 
		  session -> {
			//Account-Session 首次创建时才会被执行的方法:
			session.setType(SaTokenConsts.SESSION_TYPE__ACCOUNT);
			session.setLoginType(getLoginType());
			session.setLoginId(loginId);
		});
   }    
   --->
   //拼接: 在保存 Account-Session 时,应该使用的 key
   //key = Token:login:session:582729269957048452(uuid值)
   public String splicingKeySession(Object loginId) {
		return getConfigOrGlobal().getTokenName() 
		+ ":" + loginType + ":session:" + loginId;
	}

Redis展示:
在这里插入图片描述
2)单端独享Session(Token-Session)
Sa-Token针对会话登录,不仅为账号id分配了Account-Session,同时还为每个token分配了不同的Token-Session。当同一账号在两个设备登录,但是它们产生两个不同token和对应的Token-Session,
实现不同端会话Session的分隔、独享。

前提:用户登录完成,并将产生的Token存储至请求作用域的读取值对象SaStorage[Storage Model]内

//登录web端 – 成功 --产生token
StpUtil.login(defUser.getId(), “PC”);
追溯用户唯一值token 存储到SaTokenContext 上下文

   public void login(Object id, SaLoginModel loginModel) {
        ......
        //当前客户端注入 token
        this.setTokenValue(token, loginModel);
    }
    --->
    public void setTokenValue(String tokenValue, 
    SaLoginModel loginModel) {
        if (!SaFoxUtil.isEmpty(tokenValue)) {
             //token 写入到当前请求的 Storage 存储器里
            this.setTokenValueToStorage(tokenValue);
			.....
        }
    }
    ---->
    //将 token 写入到当前请求的 Storage 存储器里
    public void setTokenValueToStorage(String tokenValue){
		//实例化saTokenContext
		SaTokenContext saTokenContext = SaManager.
		               getSaTokenContextOrSecond();
		// 1、获取当前请求的 Storage 存储器
		SaStorage storage = saTokenContext.getStorage();
		// 2、保存 token
		String tokenPrefix = getConfigOrGlobal().getTokenPrefix();
		if( SaFoxUtil.isEmpty(tokenPrefix) ) {
			storage.set(splicingKeyJustCreatedSave(), tokenValue);
		} else {
			storage.set(splicingKeyJustCreatedSave(), 
			tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT 
			+ tokenValue);
		}
		// 3、以无前缀的方式再写入一次
		storage.set(SaTokenConsts.JUST_CREATED_NOT_PREFIX, 
		tokenValue);  
	} 

token 写入到当前请求的 Storage 存储器展示图:
在这里插入图片描述

实例化saToken上下文-saTokenContext过程说明:

SaTokenContext saTokenContext = SaManager.getSaTokenContext-
OrSecond() 
  1. starter.jar包自动装配saToken上下文注册类-SaTokenContextRegister
  2. SaTokenContextRegister类@Bean注入SaTokenContextForSpringIn-
    JakartaServlet实例化实现SaTokenContext接口。
    在这里插入图片描述
    SaTokenContextForSpringInJakartaServlet类:
public class SaTokenContextForSpringInJakartaServlet 
    implements SaTokenContext {
    public SaRequest getRequest() {
        return new SaRequestForServlet(
        SpringMVCUtil.getRequest());
    }
    public SaResponse getResponse() {
        return new SaResponseForServlet(
        SpringMVCUtil.getResponse());
    }
    public SaStorage getStorage() {
        return new SaStorageForServlet(
        SpringMVCUtil.getRequest());
    }
    .........
 }

获取当前请求的 Storage 存储器:SaStorage storage = saTokenContext.getStorage();

public class SaTokenContextForSpringInJakartaServlet implements SaTokenContext {
    /**
	 * 获取当前请求的 Storage 包装对象
	 */
	@Override
	public SaStorage getStorage() {
		return new SaStorageForServlet(SpringMVCUtil.getRequest());
	}
	...........
}
-------->
SaStorage[Storage Model],请求作用域的读取值对象
public interface SaStorage extends SaSetValueInterface {
	/** 取值 */
	@Override
	Object get(String key);
	/** 写值 */
	@Override
	SaStorage set(String key, Object value);
}

用户调用 SaSession tokenSession = StpUtil.getTokenSession();
,从同一请求作用域的读取值对象SaStorage[Storage Model]内获取tokenValue

SaSession tokenSession = StpUtil.getTokenSession();
-------->
//1.获取同一请求作用域的读取值对象SaStorage内tokenValue
//2.SaSession 尚未创建,isCreate代表是否新建并返回
public SaSession getTokenSession(boolean isCreate) {
     String tokenValue = this.getTokenValue();
     return this.getTokenSessionByToken(tokenValue, isCreate);
}
-------->
    //获取同一请求作用域的读取值对象SaStorage内tokenValue
	public String getTokenValueNotCut(){
		// 获取相应对象
		SaStorage storage = SaHolder.getStorage();
		SaRequest request = SaHolder.getRequest();
		String tokenValue = null;
		// 1. 尝试Storage存储器里读取Attrube属性为JUST_CREATED_、JUST_CREATED_NOT_PREFIX_
		//等Key属性的值,即tokenValue
		if(storage.get(splicingKeyJustCreatedSave()) != null) {
			tokenValue = String.valueOf(storage.get(splicingKeyJustCreatedSave()));
		}........
		return tokenValue;
	}

获取tokenValue后创建tokenSession并存储到Redis

public SaSession getTokenSession(boolean isCreate) {
		String tokenValue = getTokenValue();
		............
		return getTokenSessionByToken(tokenValue, isCreate);
}
-------->
public SaSession getTokenSessionByToken(String tokenValue, boolean isCreate) {
      return this.getSessionBySessionId(
      this.splicingKeyTokenSession(tokenValue), isCreate, (Long)null, (session) -> {
      // 这里是该 Token-Session 首次创建时才会被执行的方法:
                session.setType("Token-Session");
                session.setLoginType(this.getLoginType());
                session.setToken(tokenValue);
      });
}
-------->
//拼接:在保存 Token-Session 时,应该使用的 key
public String splicingKeyTokenSession(String tokenValue) {
    return this.getConfigOrGlobal().getTokenName() + ":" 
    + this.loginType + ":token-session:" + tokenValue;
    }
-------->
/** 
	 * 获取指定 key 的 SaSession, 如果该 SaSession 尚未创建,isCreate = 是否立即新建并返回
	 * @param sessionId SessionId
	 * @param isCreate 是否新建
	 * @return Session对象 
	 */
	public SaSession getSessionBySessionId(String sessionId, boolean isCreate, 
	Long timeout, Consumer<SaSession> appendOperation) {
		// 先检查这个 SaSession 是否已经存在,如果不存在且 isCreate=true,则新建并返回
		SaSession session = getSaTokenDao().getSession(sessionId);
		if(session == null && isCreate) {
			// 创建这个 SaSession
			session = SaStrategy.instance.createSession.apply(sessionId);
			...........
			// 将这个 SaSession 入库
			getSaTokenDao().setSession(session, timeout);
		}
		return session;//Redis存储返token-session,并返回
	}

同一账号在不同设备登录后独享的Token和Token-session 的Redis展示:
在这里插入图片描述
3)自定义Session( Custom-Session)
Custom-Session不依赖特定的 账号id 或者 token,而是依赖于你提供的SessionId,
由 SaSessionCustomUtil.getSessionById(“SessionId”);
流程忽略

6.持久层扩展

可集成 Redis,重启数据不丢失。
pom.xml 依赖

        <!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-redis-jackson</artifactId>
            <version>1.39.0</version>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.2</version>
        </dependency>

获取token指定的SaSession

    SaSession tokenSession = StpUtil.getTokenSessionByToken(token);
 ---------->
 	/** 
	 * 获取指定 token 的 Token-Session,如果该 SaSession 尚未创建,isCreate代表是否新建并返回
	 * @param tokenValue token值
	 * @param isCreate 是否新建 
	 * @return session对象
	 */
	public SaSession getTokenSessionByToken(String tokenValue, boolean isCreate) {
		return getSessionBySessionId(splicingKeyTokenSession(tokenValue), isCreate, null, session -> {
			session.setType(SaTokenConsts.SESSION_TYPE__TOKEN);
			session.setLoginType(getLoginType());
			session.setToken(tokenValue);
		});
	}
---------->
	SaSession session = getSaTokenDao().getSession(sessionId);
	---------->	
	//返回当前 StpLogic 使用的持久化对象
	public SaTokenDao getSaTokenDao() {
		return SaManager.getSaTokenDao();
	}
	---------->	
	public interface SaTokenDao {
		default SaSession getSession(String sessionId) {
			return (SaSession)getObject(sessionId);
		}
	}
	---------->	
	@Component
    public class SaTokenDaoRedisJackson implements SaTokenDao {
	    public Object getObject(String key) {
	        return this.objectRedisTemplate.opsForValue().get(key);
	    }
    }

Sa-Token 持久层接口SaTokenDao实现类:SaTokenDaoRedisJackson、SaTokenDaoDefaultImpl。

  • SaTokenDaoRedisJackson:持久层接口实现类(基于redis),由sa-token-redis-jackson.1.39.0.jar包自动装配[org.springframework.boot.autoconfigure.EnableAutoConfiguration
  • =cn.dev33.satoken.dao.SaTokenDaoRedisJackson]
  • SaTokenDaoDefaultImpl:持久层接口默认实现类(基于内存 Map,系统重启后数据丢失)

7.全局侦听器

在用户登陆、注销、被踢下线等关键性操作时进行一些AOP操作。
Sa-Token 提供一种侦听器机制,通过注册侦听器,你可以订阅框架的一些关键性事件,例如:用户登录、退出、被踢下线等。
在这里插入图片描述
侦听器实现参考

用户退出流程

/**
	 * [work] 会话注销,根据账号id 和 注销参数
	 * @param loginId 账号id
	 * @param logoutParameter 注销参数
	 */
	public void _logout(Object loginId, SaLogoutParameter logoutParameter) {
		// 1、获取此账号的 Account-Session,上面记录了此账号的所有登录客户端数据
		SaSession session = getSessionByLoginId(loginId, false);
		session.logoutByTerminalCountToZero();
	}
	--------->
	/** 注销Session (从持久库删除) */
	public void logout() {
		SaManager.getSaTokenDao().deleteSession(this.id);
 		// $$ 发布事件 
		SaTokenEventCenter.doLogoutSession(id);
	}
	--------->
	//Sa-Token 事件中心 事件发布器:提供侦听器注册、事件发布能力
	public class SaTokenEventCenter {
		public static void doLogoutSession(String id) {
			for (SaTokenListener listener : listenerList) {
				listener.doLogoutSession(id);
			}
		}
	}
	--------->
	public class SaTokenListenerForLog implements SaTokenListener {
	    @Override
		public void doLogoutSession(String id) {
			log.info("SaSession [{}] 注销成功", id);
		}
    }

8.全局过滤器

Sa-Token全局过滤器来实现路由拦截器鉴权。
方便的处理跨域,全局设置安全响应头等操作。
实现参考

9.多账号体系认证

一个系统多套账号分开鉴权(比如商城的 User 表和 Admin 表)
实现参考

10.单点登录

在多个互相信任的系统中,用户只需登录一次,就可以访问所有系统。
参考


http://www.kler.cn/a/612716.html

相关文章:

  • UniApp和微信小程序中v-switch夜间模式动画开关
  • Vulnhub:Digitalword.local: FALL靶机渗透
  • 【科研绘图系列】R语言绘制PCA与变量的相关性散点图(scatter plot)
  • Git回退文件到指定提交
  • C++多线程编程简介
  • NetMizer-日志管理系统-远程命令执行漏洞挖掘
  • 经典优化算法:遗传算法(Genetic Algorithm, GA)
  • Python正则表达式(二)
  • docker中安装 python
  • GPT-SoVITS本地部署:低成本实现语音克隆远程生成音频全流程实战
  • 课程5. 机器学习的核心方法
  • 简单介绍My—Batis
  • 亚马逊云科技全面托管DeepSeek-R1模型现已上线
  • 解决 Gin Web 应用中 Air 热部署无效的问题
  • pyqt第一个窗口程序
  • el-table下的复选框关联勾选
  • 【leetcode hot 100 74】搜索二维矩阵
  • 我的创作纪念日——三周年
  • [识记]Mysql8 远程授权
  • 北斗导航 | 改进奇偶矢量法的接收机自主完好性监测算法原理,公式,应用,RAIM算法研究综述,matlab代码