返回顶部
首页 > 资讯 > 后端开发 > JAVA >Sa-Token浅谈
  • 888
分享到

Sa-Token浅谈

java鉴权sa-token 2023-08-18 13:08:42 888人浏览 八月长安
摘要

主要介绍Sa-Token的鉴权使用以及实现原理。 文章目录 简介使用源码解释创建会话1.前置检查2.获取配置3.分配token4.获取 User-Session5.设置token-id映射关系6.登录成功事件发布7.检查会话数

主要介绍Sa-Token的鉴权使用以及实现原理。

简介

官网介绍的非常详细,主要突出这是一个轻量级鉴权框架的特点,详情可自行访问:https://sa-token.dev33.cn/doc.html#/

image-20221213155942848

使用

旨在简单使用,大部分功能均可以在一行代码内实现,这里举几个官网示例:

首先添加依赖:

    cn.dev33    sa-token-spring-boot-starter    1.33.0

yaml配置文件:

server:    # 端口    port: 8081############## Sa-Token 配置 (文档: Https://sa-token.cc) ##############sa-token:     # token名称 (同时也是cookie名称)    token-name: satoken    # token有效期,单位s 默认30天, -1代表永不过期     timeout: 2592000    # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒    activity-timeout: -1    # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)     is-concurrent: true    # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)     is-share: true    # token风格    token-style: uuid    # 是否输出操作日志     is-log: false
// 会话登录,参数填登录人的账号id StpUtil.login(10001);
// 校验当前客户端是否已经登录,如果未登录则抛出 `NotLoginException` 异常StpUtil.checkLogin();
// 将账号id为 10077 的会话踢下线 StpUtil.kickout(10077);

功能图如下所示:

sa-token-js

源码解释

解释登录原理

StpUtil.login(usId);

这短短的一句话代码蕴藏了多少玄机,我们一探究竟。

首先我们观察现象,模拟一个登录入口/login,里面做一个最简单的动作,就是将前端传入的usId作为用户id并交给StpUtil执行登录逻辑,并最终将usId返回。

@GetMapping("/login")private String login(String usId){    StpUtil.login(usId);    return usId;}

访问 http://localhost:8081/login?usId=123,成功返回结果123。

随后我们F12查看浏览器的控制台,Application->打开Cookies:

image-20221214103335083

能够观察到Cookies自动新增了一个键为satoken的cookie键值对,值类似于uuid随机字符串,此处示例为9ea38efb-228f-4131-b844-903467caf205,过期时间设置了30天,可对应前面配置文件的配置信息,satoken的cookie名称以及过期时间均支持配置调整。


接下来我们分析源码:

首先通过StpUtil作为入口,实际上通过实例化StpLogic来进行调用:

// StpUtil.javapublic static StpLogic stpLogic = new StpLogic("login");public static void login(Object id) {    stpLogic.login(id);}

随后在StpLogic中,通过传入的Object id以及SaLoginModel进行构建会话:

// StpLogic.javapublic void login(Object id) {    this.login(id, new SaLoginModel());}// 通过这两个方法进行会话建立 createLoginSession()/setTokenValue()public void login(Object id, SaLoginModel loginModel) {    // 1、创建会话     String token = this.createLoginSession(id, loginModel);    // 2、在当前客户端注入Token     setTokenValue(token, loginModel);}

创建会话

分析StpLogic.createLoginSession()方法

public String createLoginSession(Object id, SaLoginModel loginModel) {    // 1.这个设计可以借鉴一下,直接可以判断id值为null就抛异常,将异常封装起来    SaTokenException.throwByNull(id, "账号id不能为空", 11002);    // 2.获取配置信息    SaTokenConfig config = this.getConfig();    loginModel.build(config);    // 3.生成token(若已经有存在生效的token则使用原先的token)    String tokenValue = this.distUsableToken(id, loginModel);    // 4.获取 User-Session/首次登录则创建会话,使用自定义封装的SaSession对象接收    SaSession session = this.getSessionByLoginId(id, true);    session.updateMinTimeout(loginModel.getTimeout());    session.addTokenSign(tokenValue, loginModel.getDeviceOrDefault());    // 5.设置token -> id 映射关系      this.saveTokenToIdMapping(tokenValue, id, loginModel.getTimeout());    // 调用getSaTokenDao().set设置tokenValue与失效时间的关系    this.setLastActivityToNow(tokenValue);    // 6.事件发布    SaTokenEventCenter.doLogin(this.loginType, id, tokenValue, loginModel);    // 7.检查此账号会话数量是否超出最大值,-1表示不限会话数量    if (config.getMaxLoginCount() != -1) {        loGoutByMaxLoginCount(id, session, (String)null, config.getMaxLoginCount());    }    return tokenValue;}

1.前置检查

判断Object id是否为null

 SaTokenException.throwByNull(id, "账号id不能为空", 11002);public static void throwByNull(Object value, String message, int code) {    if(SaFoxUtil.isEmpty(value)) {        throw new SaTokenException(message).setCode(code);    }}

2.获取配置

StpLogic.getConfig():

// 2.获取配置信息SaTokenConfig config = this.getConfig();

image-20221214104609553

3.分配token

StpLogic.distUsableToken():

protected String distUsableToken(Object id, SaLoginModel loginModel) {    Boolean isConcurrent = this.getConfig().getIsConcurrent();    if (!isConcurrent) {        this.replaced(id, loginModel.getDevice());    }    if (SaFoxUtil.isNotEmpty(loginModel.getToken())) {        return loginModel.getToken();    } else {        if (isConcurrent && this.getConfigOfIsshare() && !loginModel.isSetExtraData()) {            // 获取token            String tokenValue = this.getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());            // 已经存在会话,将之前生成的token返回            if (SaFoxUtil.isNotEmpty(tokenValue)) {                return tokenValue;            }        }// *新会话,生成一个新token,可借鉴,见下方解析        return this.createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData());    }}

解析StpLogic.createTokenValue():

public class StpLogic {    // ...        public String createTokenValue(Object loginId, String device, long timeout, Map extraData) {        // SaStrategy.me:使用SaStrategy的单例引用        // SaStrategy.me.createToken:调用createToken()方法        // SaStrategy.me.createToken.apply:使得结果生效        return SaStrategy.me.createToken.apply(loginId, loginType);    }}public final class SaStrategy {    public static final SaStrategy me = new SaStrategy();        public BiFunction createToken = (loginId, loginType) -> {// 根据配置的tokenStyle生成不同风格的token String tokenStyle = SaManager.getConfig().getTokenStyle();// uuid if(SaTokenConsts.TOKEN_STYLE_UUID.equals(tokenStyle)) {return UUID.randomUUID().toString();}// 简单uuid (不带下划线)if(SaTokenConsts.TOKEN_STYLE_SIMPLE_UUID.equals(tokenStyle)) {return UUID.randomUUID().toString().replaceAll("-", "");}// 32位随机字符串if(SaTokenConsts.TOKEN_STYLE_RANDOM_32.equals(tokenStyle)) {return SaFoxUtil.getRandomString(32);}// 64位随机字符串if(SaTokenConsts.TOKEN_STYLE_RANDOM_64.equals(tokenStyle)) {return SaFoxUtil.getRandomString(64);}// 128位随机字符串if(SaTokenConsts.TOKEN_STYLE_RANDOM_128.equals(tokenStyle)) {return SaFoxUtil.getRandomString(128);}// tik风格 (2_14_16)if(SaTokenConsts.TOKEN_STYLE_TIK.equals(tokenStyle)) {return SaFoxUtil.getRandomString(2) + "_" + SaFoxUtil.getRandomString(14) + "_" + SaFoxUtil.getRandomString(16) + "__";}// 默认,还是uuid return UUID.randomUUID().toString();};    }

4.获取 User-Session

StpLogic.getSessionByLoginId()

public SaSession getSessionByLoginId(Object loginId, boolean isCreate) {    return getSessionBySessionId(splicingKeySession(loginId), isCreate);}public SaSession getSessionBySessionId(String sessionId, boolean isCreate) {    // getSaTokenDao使用了懒加载初始化SaTokenDao对象,最终由new SaTokenDaoDefaultImpl()进行实现具体方法    // 并根据sessionId获取session    SaSession session = getSaTokenDao().getSession(sessionId);    // session暂未建立,进行session新建    if(session == null && isCreate) {        // 与上方的createToken使用了同样的设计        session = SaStrategy.me.createSession.apply(sessionId);        // 设置session与session过期时效        getSaTokenDao().setSession(session, getConfig().getTimeout());    }    return session;}

5.设置token-id映射关系

StpLogic.saveTokenToIdMapping()

public void saveTokenToIdMapping(String tokenValue, Object loginId, long timeout) {// 如果继续往下深挖其实set方法的实现底层就是一个new ConcurrentHashMap()    // 并且封装了一个new ConcurrentHashMap()来记录key与过期时间的关系    // 并且设置的过期过期时间    getSaTokenDao().set(splicingKeyTokenValue(tokenValue), String.valueOf(loginId), timeout);}

这里浅浅看一下设置的过期时间如何实现:

public Map dataMap = new ConcurrentHashMap();public Map expireMap = new ConcurrentHashMap();@Overridepublic void set(String key, String value, long timeout) {    if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE)  {        return;    }    // 设置key-value    dataMap.put(key, value);    // 设置key-到期时间    expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000));}void clearKeyByTimeout(String key) {    Long expirationTime = expireMap.get(key);    // 清除条件:如果不为空 && 不是[永不过期] && 已经超过过期时间     if(expirationTime != null && expirationTime != SaTokenDao.NEVER_EXPIRE && expirationTime < System.currentTimeMillis()) {        dataMap.remove(key);        expireMap.remove(key);    }}// ------------------------ String 读写操作 @Overridepublic String get(String key) {    // 首先判断一下key是否已经过期    clearKeyByTimeout(key);    return (String)dataMap.get(key);}

6.登录成功事件发布

SaTokenEventCenter.doLogin

// --------- 注册侦听器 private static List listenerList = new ArrayList<>();public static void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {    for (SaTokenListener listener : listenerList) {        listener.doLogin(loginType, loginId, tokenValue, loginModel);    }}

主要目的是登录成功后的一些后置处理方法回调等,通过观察我们可以发现只要注册到SaTokenEventCenter.listenerList中即可在遍历中执行监听器的doLogin()方法。目前默认添加控制台日志侦听器,new SaTokenListenerForLog(),主要实现一些日志打印:

image-20221214140145488

还有一个空实现的SaTokenListenerForSimple监听器,后续我们如果想要做一些自定义扩展,就可以继承SaTokenListenerForSimple做一些属于我们自己的业务监听器处理:

image-20221214140335284

7.检查会话数量

StpLogic.logoutByMaxLoginCount()

public void logoutByMaxLoginCount(Object loginId, SaSession session, String device, int maxLoginCount) {    if(session == null) {        session = getSessionByLoginId(loginId, false);        if(session == null) {            return;        }    }    List list = session.tokenSignListCopyByDevice(device);    // 遍历操作     for (int i = 0; i < list.size(); i++) {        // 只操作前n条         if(i >= list.size() - maxLoginCount) {            continue;        }        // 清理: token签名、token最后活跃时间         String tokenValue = list.get(i).getValue();        session.removeTokenSign(tokenValue);         clearLastActivity(tokenValue);         // 删除Token-Id映射 & 清除Token-Session         deleteTokenToIdMapping(tokenValue);        deleteTokenSession(tokenValue);        // $$ 发布事件:指定账号注销         SaTokenEventCenter.doLogout(loginType, loginId, tokenValue);    }    // 注销 Session     session.logoutByTokenSignCountToZero();}

客户端注入Token

分析StpLogic.setTokenValue()方法

public void setTokenValue(String tokenValue, SaLoginModel loginModel){    if(SaFoxUtil.isEmpty(tokenValue)) {        return;    }    // 1. 将 Token 保存到 [存储器] 里      setTokenValueToStorage(tokenValue);    // 2. 将 Token 保存到 [Cookie] 里 此处对应    if (getConfig().getIsReadCookie()) {        setTokenValueToCookie(tokenValue, loginModel.getCookieTimeout());    }    // 3. 将 Token 写入到响应头里     if(loginModel.getIsWriteHeaderOrGlobalConfig()) {        setTokenValueToResponseHeader(tokenValue);    }}

参考资料:

来源地址:https://blog.csdn.net/imVainiycos/article/details/128319460

--结束END--

本文标题: Sa-Token浅谈

本文链接: https://lsjlt.com/news/373988.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Sa-Token浅谈
    主要介绍Sa-Token的鉴权使用以及实现原理。 文章目录 简介使用源码解释创建会话1.前置检查2.获取配置3.分配token4.获取 User-Session5.设置token-id映射关系6.登录成功事件发布7.检查会话数...
    99+
    2023-08-18
    java 鉴权 sa-token
  • SpringBoot使用Sa-Token实现登录认证
    目录一、设计思路二、登录与注销三、会话查询四、Token 查询五、来个小测试,加深一下理解一、设计思路 对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加...
    99+
    2023-05-14
    SpringBoot Sa-Token登录认证 SpringBoot 登录认证
  • SpringBoot使用Sa-Token实现权限认证
    目录一、设计思路二、获取当前账号权限码集合三、权限校验四、角色校验五、拦截全局异常六、权限通配符七、如何把权限精确到按钮级?八、前端有了鉴权后端还需要鉴权吗?九、来个小示例,加深一下...
    99+
    2023-05-17
    SpringBoot Sa-Token权限认证 SpringBoot 权限认证
  • springboot整合sa-token简介及入门教程
    目录springboot:整合sa-token一、简介二、入门程序1.添加依赖2.sa-token配置类3.测试controller三、认证登录1.mysql配置类2.实体类3.ma...
    99+
    2023-05-20
    springboot 整合sa-token springboot sa-token
  • 浅谈node使用jwt生成的token应该存在哪里
    答:通常存储在客户端里。 jwt 即 JSON Web Token,是一种认证协议,一般用来校验请求的身份信息和身份权限。 早上逛某乎的时候,遇到一位同学在问这个问题,很好奇jwt的...
    99+
    2024-04-02
  • SpringBoot怎么使用Sa-Token实现登录认证
    这篇文章主要讲解了“SpringBoot怎么使用Sa-Token实现登录认证”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SpringBoot怎么使用Sa-Token实现登录认证”吧!一、设...
    99+
    2023-07-05
  • SpringBoot如何使用Sa-Token实现权限认证
    今天小编给大家分享一下SpringBoot如何使用Sa-Token实现权限认证的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。...
    99+
    2023-07-06
  • Java轻量级权限认证框架Sa-Token的使用
    目录前言Sa-Token 是什么如何使用?引入依赖示例代码常用方法常用注解注册拦截器Spring boot 集成1、在application.yml配置2、通过代码配置总结前言 相信...
    99+
    2024-04-02
  • Java使用Sa-Token框架完成踢人下线功能
    目录一、需求二、踢人下线 API 一览三、根据账号踢人下线四、根据 Token 踢人下线参考资料一、需求 在企业级项目中,踢人下线是一个很常见的需求,如果要设计比较完善的话,至少需要...
    99+
    2023-05-19
    Sa-Token 踢人下线
  • Android UIAutomator浅谈
      Android UIAutomator浅谈   简介   Uiautomator是谷歌推出的,用于UI自动化测试的工具,也是普通的手工测试,点击每个控件元素看看输出...
    99+
    2022-06-06
    uiautomator Android
  • 浅谈Vue DIFF
    目录不带 key 的操作带 key 的操作简单 DIFFVue是如何找到需要进行移动的元素Vue是如何移动元素的Vue是如何进行新增元素的Vue 是如何删除多余的旧元素的双指针 DI...
    99+
    2023-05-19
    Vue DIFF
  • 浅谈 TLS 1.3
    本文主要从TLS 1.3的优势、部署和时间发展线介绍了这种用于为计算机网络通信提供安全性的密码协议TLS。 TLS简介按照维基百科的定义,TLS 是一种用于为计算机网络通信提供安全性的密码协议,其前身安全套接层(SSL)想必很多人...
    99+
    2023-06-03
  • SpringBoot整合Sa-Token实现登录认证的示例代码
    目录依赖登录退出登录前后端分离今天分享的是 Spring Boot 整合 Sa-Token 实现登录认证。 依赖 首先,我们需要添加依赖: 关...
    99+
    2024-04-02
  • SpringBoot 使用 Sa-Token 完成注解鉴权功能(权限校验)
    目录一、Sa-Token 鉴权注解一览二、登录认证三、权限认证 & 角色认证四、设定校验模式五、角色权限双重 “or校验”六、二级认证七、HttpBa...
    99+
    2023-05-20
    SpringBoot  Sa-Token 注解鉴权 SpringBoot  Sa-Token 鉴权
  • 浅谈Oracle索引
    Oracle中查询走索引的情况: 对返回的行无任何限定条件,即没有where子句。 未对数据表与任何索引主列相对应的行限定条件。 例如:在id-name-time列创建了三列复合索引,那么仅对name列限定条件不能使用这个索...
    99+
    2014-07-01
    浅谈Oracle索引
  • 浅谈Node.js:Buffer模块
    Javascript在客户端对于unicode编码的数据操作支持非常友好,但是对二进制数据的处理就不尽人意。Node.js为了能够处理二进制数据或非unicode编码的数据,便设计了Buffer类,该类实现...
    99+
    2022-06-04
    浅谈 模块 js
  • 浅谈MySQL函数
    目录1、数学函数2、字符串函数3、日期函数4、加密函数 主要MySQL函数介绍又以下: 数学函数 字符串函数 时间函数 加密函数 ...
    99+
    2024-04-02
  • 浅谈基础RCE
    一、什么是rce 二、能简单利用达到rce目的那些函数 1.eval() 2.assert()断言函数 3.call_user_func()&call_user_func_array()回调函数 4.create_function() 三...
    99+
    2023-09-03
    安全 服务器 web安全
  • 浅谈fis3与postcss
    Fis3构建工具Fis3的安装npm install -g fis3查看fis3fis3 –vfi3构建工具新建文件夹新建一个根目录进入根目录输入fis3 init指令初始化发布以及监听文件 指令:fis3 release –w –d ./...
    99+
    2023-01-31
    浅谈 postcss
  • 浅谈#!/usr/bin/python3
    原文:https://finthon.com/python-shebang/ 简介 在Python脚本的第一行,常常能看到#!/usr/bin/env python3或者#!/usr/bin/python3字样,其中#!符号在计算机行业中叫...
    99+
    2023-01-31
    浅谈 usr bin
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作