jwt集成新增mix模式

This commit is contained in:
click33 2021-10-20 22:49:54 +08:00
parent f9ec6e6487
commit bb5c378f48
11 changed files with 545 additions and 45 deletions

View File

@ -0,0 +1,31 @@
package cn.dev33.satoken.exception;
/**
* 一个异常代表 API 已被禁用
* @author kong
*/
public class ApiDisabledException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130133L;
/** 异常提示语 */
public static final String BE_MESSAGE = "This API is disabled";
/**
* 一个异常代表 API 已被禁用
*/
public ApiDisabledException() {
super(BE_MESSAGE);
}
/**
* 一个异常代表 API 已被禁用
* @param message 异常描述
*/
public ApiDisabledException(String message) {
super(message);
}
}

View File

@ -84,14 +84,16 @@ public class StpLogic {
}
/**
* 创建一个TokenValue
* @param loginId loginId
* 创建一个TokenValue
* @param loginId loginId
* @param device 设备标识
* @param timeout 过期时间
* @return 生成的tokenValue
*/
public String createTokenValue(Object loginId) {
public String createTokenValue(Object loginId, String device, long timeout) {
return SaStrategy.me.createToken.apply(loginId, loginType);
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
@ -255,7 +257,7 @@ public class StpLogic {
// --- 如果允许并发登录
if(config.getIsConcurrent()) {
// 如果配置为共享token, 则尝试从Session签名记录里取出token
if(config.getIsShare()) {
if(getConfigOfIsShare()) {
tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
}
} else {
@ -264,7 +266,7 @@ public class StpLogic {
}
// 如果至此仍未成功创建tokenValue, 则开始生成一个
if(tokenValue == null) {
tokenValue = createTokenValue(id);
tokenValue = createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout());
}
// ------ 3. 获取 User-Session , 续期
@ -767,7 +769,7 @@ public class StpLogic {
String tokenValue = getTokenValue();
if(tokenValue == null || Objects.equals(tokenValue, "")) {
// 随机一个token送给Ta
tokenValue = createTokenValue(null);
tokenValue = createTokenValue(null, null, getConfig().getTimeout());
// 写入 [最后操作时间]
setLastActivityToNow(tokenValue);
// 在当前会话写入这个tokenValue
@ -1621,6 +1623,14 @@ public class StpLogic {
return SaManager.getConfig();
}
/**
* 返回全局配置对象的isShare属性
* @return /
*/
public boolean getConfigOfIsShare() {
return getConfig().getIsShare();
}
/**
* 返回持久化对象
* @return /

View File

@ -6,7 +6,7 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.jwt.StpLogicJwtForStateless;
import cn.dev33.satoken.jwt.StpLogicJwtForTokenStyle;
import cn.dev33.satoken.stp.StpLogic;
@ -32,7 +32,7 @@ public class SaTokenConfigure implements WebMvcConfigurer {
*/
@Bean
public StpLogic getStpLogicJwt() {
return new StpLogicJwtForStateless();
return new StpLogicJwtForTokenStyle();
}
}

View File

@ -0,0 +1,256 @@
package com.pj.test;
import java.util.List;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.ApiDisabledException;
import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.jwt.SaJwtUtil;
import cn.dev33.satoken.jwt.StpLogicJwtForMix;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
/**
* Sa-Token 整合 jwtmix 模式 测试
*
* @author kong
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartUpApplication.class)
public class JwtForMixTest {
// 持久化Bean
@Autowired(required = false)
SaTokenDao dao = SaManager.getSaTokenDao();
// 开始
@BeforeClass
public static void beforeClass() {
System.out.println("\n\n------------------------ JwtForMixTest star ...");
StpUtil.setStpLogic(new StpLogicJwtForMix());
}
// 结束
@AfterClass
public static void afterClass() {
System.out.println("\n\n------------------------ JwtForMixTest end ... \n");
}
// 测试登录
@Test
public void doLogin() {
// 登录
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
// API 验证
Assert.assertTrue(StpUtil.isLogin());
Assert.assertNotNull(token); // token不为null
Assert.assertEquals(StpUtil.getLoginIdAsLong(), 10001); // loginId=10001
Assert.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备
// token 验证
JWT jwt = JWT.of(token);
JSONObject payloads = jwt.getPayloads();
Assert.assertEquals(payloads.getStr(SaJwtUtil.LOGIN_ID), "10001"); // 账号
Assert.assertEquals(payloads.getStr(SaJwtUtil.DEVICE), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备
Assert.assertEquals(payloads.getStr(SaJwtUtil.LOGIN_TYPE), StpUtil.TYPE); // 账号类型
// db数据 验证
// token不存在
Assert.assertNull(dao.get("satoken:login:token:" + token));
// Session 存在
SaSession session = dao.getSession("satoken:login:session:" + 10001);
Assert.assertNotNull(session);
Assert.assertEquals(session.getId(), "satoken:login:session:" + 10001);
Assert.assertTrue(session.getTokenSignList().size() >= 1);
}
// 测试注销
@Test
public void logout() {
// 登录
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
Assert.assertEquals(JWT.of(token).getPayloads().getStr("loginId"), "10001");
// 注销
StpUtil.logout();
// token 应该被清除
Assert.assertNull(StpUtil.getTokenValue());
Assert.assertFalse(StpUtil.isLogin());
}
// 测试Session会话
@Test
public void testSession() {
StpUtil.login(10001);
// API 应该可以获取 Session
Assert.assertNotNull(StpUtil.getSession(false));
// db中应该存在 Session
SaSession session = dao.getSession("satoken:login:session:" + 10001);
Assert.assertNotNull(session);
// 存取值
session.set("name", "zhang");
session.set("age", "18");
Assert.assertEquals(session.get("name"), "zhang");
Assert.assertEquals(session.getInt("age"), 18);
Assert.assertEquals((int)session.getModel("age", int.class), 18);
Assert.assertEquals((int)session.get("age", 20), 18);
Assert.assertEquals((int)session.get("name2", 20), 20);
Assert.assertEquals((int)session.get("name2", () -> 30), 30);
session.clear();
Assert.assertEquals(session.get("name"), null);
}
// 测试权限认证
@Test
public void testCheckPermission() {
StpUtil.login(10001);
// 权限认证
Assert.assertTrue(StpUtil.hasPermission("user-add"));
Assert.assertTrue(StpUtil.hasPermission("user-list"));
Assert.assertTrue(StpUtil.hasPermission("user"));
Assert.assertTrue(StpUtil.hasPermission("art-add"));
Assert.assertFalse(StpUtil.hasPermission("get-user"));
// and
Assert.assertTrue(StpUtil.hasPermissionAnd("art-add", "art-get"));
Assert.assertFalse(StpUtil.hasPermissionAnd("art-add", "comment-add"));
// or
Assert.assertTrue(StpUtil.hasPermissionOr("art-add", "comment-add"));
Assert.assertFalse(StpUtil.hasPermissionOr("comment-add", "comment-delete"));
}
// 测试角色认证
@Test
public void testCheckRole() {
StpUtil.login(10001);
// 角色认证
Assert.assertTrue(StpUtil.hasRole("admin"));
Assert.assertFalse(StpUtil.hasRole("teacher"));
// and
Assert.assertTrue(StpUtil.hasRoleAnd("admin", "super-admin"));
Assert.assertFalse(StpUtil.hasRoleAnd("admin", "ceo"));
// or
Assert.assertTrue(StpUtil.hasRoleOr("admin", "ceo"));
Assert.assertFalse(StpUtil.hasRoleOr("ceo", "cto"));
}
// 测试根据token强制注销
@Test(expected = ApiDisabledException.class)
public void testLogoutByToken() {
// 先登录上
StpUtil.login(10001);
Assert.assertTrue(StpUtil.isLogin());
String token = StpUtil.getTokenValue();
// 根据token注销
StpUtil.logoutByTokenValue(token);
}
// 测试根据账号id强制注销
@Test(expected = ApiDisabledException.class)
public void testLogoutByLoginId() {
// 先登录上
StpUtil.login(10001);
Assert.assertTrue(StpUtil.isLogin());
// 根据账号id注销
StpUtil.logout(10001);
}
// 测试Token-Session
@Test
public void testTokenSession() {
// 先登录上
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
// 刚开始不存在
Assert.assertNull(StpUtil.stpLogic.getTokenSession(false));
SaSession session = dao.getSession("satoken:login:token-session:" + token);
Assert.assertNull(session);
// 调用一次就存在了
StpUtil.getTokenSession();
Assert.assertNotNull(StpUtil.stpLogic.getTokenSession(false));
SaSession session2 = dao.getSession("satoken:login:token-session:" + token);
Assert.assertNotNull(session2);
}
// 测试账号封禁
@Test(expected = DisableLoginException.class)
public void testDisable() {
// 封号
StpUtil.disable(10007, 200);
Assert.assertTrue(StpUtil.isDisable(10007));
Assert.assertEquals(dao.get("satoken:login:disable:" + 10007), DisableLoginException.BE_VALUE);
// 解封
StpUtil.untieDisable(10007);
Assert.assertFalse(StpUtil.isDisable(10007));
Assert.assertEquals(dao.get("satoken:login:disable:" + 10007), null);
// 封号后登陆 (会抛出 DisableLoginException 异常)
StpUtil.disable(10007, 200);
StpUtil.login(10007);
}
// 测试身份切换
@Test
public void testSwitch() {
// 登录
StpUtil.login(10001);
Assert.assertFalse(StpUtil.isSwitch());
Assert.assertEquals(StpUtil.getLoginIdAsLong(), 10001);
// 开始身份切换
StpUtil.switchTo(10044);
Assert.assertTrue(StpUtil.isSwitch());
Assert.assertEquals(StpUtil.getLoginIdAsLong(), 10044);
// 结束切换
StpUtil.endSwitch();
Assert.assertFalse(StpUtil.isSwitch());
Assert.assertEquals(StpUtil.getLoginIdAsLong(), 10001);
}
// 测试会话管理
@Test(expected = ApiDisabledException.class)
public void testSearchTokenValue() {
// 登录
StpUtil.login(10001);
StpUtil.login(10002);
StpUtil.login(10003);
StpUtil.login(10004);
StpUtil.login(10005);
// 查询
List<String> list = StpUtil.searchTokenValue("", 0, 10);
Assert.assertTrue(list.size() >= 5);
}
}

View File

@ -5,12 +5,13 @@ import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.exception.ApiDisabledException;
import cn.dev33.satoken.jwt.SaJwtUtil;
import cn.dev33.satoken.jwt.StpLogicJwtForStateless;
import cn.dev33.satoken.stp.StpUtil;
@ -21,7 +22,6 @@ import cn.hutool.jwt.JWT;
/**
* Sa-Token 整合 jwtstateless 模式 测试
*
*
* @author kong
*
*/
@ -30,20 +30,20 @@ import cn.hutool.jwt.JWT;
public class JwtForStatelessTest {
// 持久化Bean
static SaTokenDao dao;
@Autowired(required = false)
SaTokenDao dao = SaManager.getSaTokenDao();
// 开始
@BeforeClass
public static void beforeClass() {
System.out.println("\n\n------------------------ 基础测试 star ...");
dao = SaManager.getSaTokenDao();
System.out.println("\n\n------------------------ JwtForStatelessTest star ...");
StpUtil.setStpLogic(new StpLogicJwtForStateless());
}
// 结束
@AfterClass
public static void afterClass() {
System.out.println("\n\n------------------------ 基础测试 end ... \n");
System.out.println("\n\n------------------------ JwtForStatelessTest end ... \n");
}
// 测试登录
@ -95,7 +95,7 @@ public class JwtForStatelessTest {
}
// 测试Session会话
@Test(expected = SaTokenException.class)
@Test(expected = ApiDisabledException.class)
public void testSession() {
StpUtil.login(10001);
@ -139,7 +139,7 @@ public class JwtForStatelessTest {
}
// 测试根据token强制注销
@Test(expected = SaTokenException.class)
@Test(expected = ApiDisabledException.class)
public void testLogoutByToken() {
// 先登录上
@ -152,7 +152,7 @@ public class JwtForStatelessTest {
}
// 测试根据账号id强制注销
@Test(expected = SaTokenException.class)
@Test(expected = ApiDisabledException.class)
public void testLogoutByLoginId() {
// 先登录上

View File

@ -257,6 +257,9 @@
<a href="http://www.dzlanke.cn/" target="_blank" title="德州蓝客网络科技">
<img src="https://oss.dev33.cn/sa-token/com/dezhoulanke.png">
</a>
<a href="http://www.turingoal.com" target="_blank" title="图灵谷(北京)科技有限公司">
<img src="https://oss.dev33.cn/sa-token/com/tulinggu.png">
</a>
</div>
<div style="height: 10px; clear: both;"></div>

View File

@ -0,0 +1,209 @@
package cn.dev33.satoken.jwt;
import java.util.List;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.ApiDisabledException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
/**
* Sa-Token 整合 jwt -- Mix 混入
* @author kong
*
*/
public class StpLogicJwtForMix extends StpLogic {
/**
* Sa-Token 整合 jwt -- Mix 混入
*/
public StpLogicJwtForMix() {
super(StpUtil.TYPE);
}
/**
* Sa-Token 整合 jwt -- Mix 混入
* @param loginType 账号体系标识
*/
public StpLogicJwtForMix(String loginType) {
super(loginType);
}
/**
* 获取jwt秘钥
* @return /
*/
public String jwtSecretKey() {
return getConfig().getJwtSecretKey();
}
//
// ------ 重写方法
//
// ------------------- 获取token 相关 -------------------
/**
* 创建一个TokenValue
*/
@Override
public String createTokenValue(Object loginId, String device, long timeout) {
return SaJwtUtil.createToken(loginType, loginId, device, timeout, jwtSecretKey());
}
/**
* 获取当前会话的Token信息
* @return token信息
*/
@Override
public SaTokenInfo getTokenInfo() {
SaTokenInfo info = new SaTokenInfo();
info.tokenName = getTokenName();
info.tokenValue = getTokenValue();
info.isLogin = isLogin();
info.loginId = getLoginIdDefaultNull();
info.loginType = getLoginType();
info.tokenTimeout = getTokenTimeout();
info.sessionTimeout = SaTokenDao.NOT_VALUE_EXPIRE;
info.tokenSessionTimeout = SaTokenDao.NOT_VALUE_EXPIRE;
info.tokenActivityTimeout = SaTokenDao.NOT_VALUE_EXPIRE;
info.loginDevice = getLoginDevice();
return info;
}
// ------------------- 登录相关操作 -------------------
/**
* 获取指定Token对应的账号id (不做任何特殊处理)
*/
@Override
public String getLoginIdNotHandle(String tokenValue) {
// 先验证 loginType如果不符则视为无效token返回null
String loginType = SaJwtUtil.getPayloadsNotCheck(tokenValue, jwtSecretKey()).getStr(SaJwtUtil.LOGIN_TYPE);
if(getLoginType().equals(loginType) == false) {
return null;
}
// 获取 loginId
try {
Object loginId = SaJwtUtil.getLoginId(tokenValue, jwtSecretKey());
return String.valueOf(loginId);
} catch (NotLoginException e) {
return null;
}
}
/**
* 会话注销
*/
@Override
public void logout() {
// ...
// 从当前 [storage存储器] 里删除
SaHolder.getStorage().delete(splicingKeyJustCreatedSave());
// 如果打开了Cookie模式则把cookie清除掉
if(getConfig().getIsReadCookie()){
SaHolder.getResponse().deleteCookie(getTokenName());
}
}
/**
* [禁用] 会话注销根据账号id 设备标识
*/
@Override
public void logout(Object loginId, String device) {
throw new ApiDisabledException();
}
/**
* [禁用] 会话注销根据指定 Token
*/
@Override
public void logoutByTokenValue(String tokenValue) {
throw new ApiDisabledException();
}
/**
* [禁用] 踢人下线根据账号id 设备标识
*/
@Override
public void kickout(Object loginId, String device) {
throw new ApiDisabledException();
}
/**
* [禁用] 踢人下线根据指定 Token
*/
@Override
public void kickoutByTokenValue(String tokenValue) {
throw new ApiDisabledException();
}
/**
* [禁用] 顶人下线根据账号id 设备标识
*/
@Override
public void replaced(Object loginId, String device) {
throw new ApiDisabledException();
}
/**
* 删除 Token-Id 映射
*/
@Override
public void deleteTokenToIdMapping(String tokenValue) {
// not action
}
/**
* 更改 Token 指向的 账号Id
*/
@Override
public void updateTokenToIdMapping(String tokenValue, Object loginId) {
// not action
}
/**
* 存储 Token-Id 映射
*/
@Override
public void saveTokenToIdMapping(String tokenValue, Object loginId, long timeout) {
// not action
}
// ------------------- 过期时间相关 -------------------
/**
* 获取当前登录者的 token 剩余有效时间 (单位: )
*/
@Override
public long getTokenTimeout() {
return SaJwtUtil.getTimeout(getTokenValue(), jwtSecretKey());
}
// ------------------- 会话管理 -------------------
/**
* 根据条件查询Token
*/
@Override
public List<String> searchTokenValue(String keyword, int start, int size) {
throw new ApiDisabledException();
}
// ------------------- Bean对象代理 -------------------
/**
* 返回全局配置对象的isShare属性
* @return /
*/
@Override
public boolean getConfigOfIsShare() {
return false;
}
}

View File

@ -3,6 +3,7 @@ package cn.dev33.satoken.jwt;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.ApiDisabledException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.stp.SaLoginModel;
@ -18,12 +19,7 @@ import cn.dev33.satoken.stp.StpUtil;
public class StpLogicJwtForStateless extends StpLogic {
/**
* 异常描述
*/
public static final String ERROR_MESSAGE = "This API is disabled";
/**
* 初始化StpLogic, 并指定账号类型
* Sa-Token 整合 jwt -- stateless 无状态
* @param loginType 账号体系标识
*/
public StpLogicJwtForStateless() {
@ -31,7 +27,7 @@ public class StpLogicJwtForStateless extends StpLogic {
}
/**
* 初始化StpLogic, 并指定账号类型
* Sa-Token 整合 jwt -- stateless 无状态
* @param loginType 账号体系标识
*/
public StpLogicJwtForStateless(String loginType) {
@ -56,8 +52,8 @@ public class StpLogicJwtForStateless extends StpLogic {
* 创建一个TokenValue
*/
@Override
public String createTokenValue(Object loginId) {
return SaJwtUtil.createToken(loginId, jwtSecretKey());
public String createTokenValue(Object loginId, String device, long timeout) {
return SaJwtUtil.createToken(loginType, loginId, device, timeout, jwtSecretKey());
}
/**
@ -94,13 +90,7 @@ public class StpLogicJwtForStateless extends StpLogic {
loginModel.build(getConfig());
// ------ 2生成一个token
String tokenValue = SaJwtUtil.createToken(
loginType,
id,
loginModel.getDeviceOrDefault(),
loginModel.getTimeout(),
jwtSecretKey()
);
String tokenValue = createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout());
// 3在当前会话写入tokenValue
setTokenValue(tokenValue, loginModel.getCookieTimeout());
@ -114,7 +104,7 @@ public class StpLogicJwtForStateless extends StpLogic {
*/
@Override
public String getLoginIdNotHandle(String tokenValue) {
// 先验证 loginType如果不符相当于null
// 先验证 loginType如果不符则视为无效token返回null
String loginType = SaJwtUtil.getPayloadsNotCheck(tokenValue, jwtSecretKey()).getStr(SaJwtUtil.LOGIN_TYPE);
if(getLoginType().equals(loginType) == false) {
return null;
@ -181,11 +171,11 @@ public class StpLogicJwtForStateless extends StpLogic {
// ------------------- Bean对象代理 -------------------
/**
* 返回持久化对象
* [禁用] 返回持久化对象
*/
@Override
public SaTokenDao getSaTokenDao() {
throw new SaTokenException(ERROR_MESSAGE);
throw new ApiDisabledException();
}

View File

@ -4,15 +4,14 @@ import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
/**
* Sa-Token 整合 jwt -- Token风格
* Sa-Token 整合 jwt -- Token-Style
* @author kong
*
*/
public class StpLogicJwtForTokenStyle extends StpLogic {
/**
* 初始化StpLogic, 并指定账号类型
* @param loginType 账号体系标识
* Sa-Token 整合 jwt -- Token-Style
*/
public StpLogicJwtForTokenStyle() {
super(StpUtil.TYPE);
@ -42,7 +41,7 @@ public class StpLogicJwtForTokenStyle extends StpLogic {
* @return 生成的tokenValue
*/
@Override
public String createTokenValue(Object loginId) {
public String createTokenValue(Object loginId, String device, long timeout) {
return SaJwtUtil.createToken(loginId, jwtSecretKey());
}

View File

@ -7,6 +7,7 @@ import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@ -32,13 +33,13 @@ import cn.dev33.satoken.util.SaTokenConsts;
public class BasicsTest {
// 持久化Bean
static SaTokenDao dao;
@Autowired(required = false)
SaTokenDao dao = SaManager.getSaTokenDao();
// 开始
@BeforeClass
public static void beforeClass() {
System.out.println("\n\n------------------------ 基础测试 star ...");
dao = SaManager.getSaTokenDao();
}
// 结束

View File

@ -7,6 +7,7 @@ import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@ -28,13 +29,13 @@ import cn.dev33.satoken.stp.StpUtil;
public class ManyLoginTest {
// 持久化Bean
static SaTokenDao dao;
@Autowired(required = false)
SaTokenDao dao = SaManager.getSaTokenDao();
// 开始
@BeforeClass
public static void beforeClass() {
System.out.println("\n------------ 多端登录测试 star ...");
dao = SaManager.getSaTokenDao();
}
// 结束
@AfterClass