完善 StpUtil 单元测试

This commit is contained in:
click33 2022-09-03 09:40:27 +08:00
parent 68e35ac9bb
commit 36bcbd4f8e
3 changed files with 308 additions and 47 deletions

View File

@ -159,6 +159,17 @@ public class SaLoginModel {
return this.extraData.get(key);
}
/**
* 判断是否设置了扩展数据
* @return /
*/
public boolean isSetExtraData() {
if(extraData == null || extraData.size() == 0) {
return false;
}
return true;
}
/**
* @return Cookie时长
*/

View File

@ -311,10 +311,10 @@ public class StpLogic {
*/
public String createLoginSession(Object id, SaLoginModel loginModel) {
// ------ 前置检查
SaTokenException.throwByNull(id, "账号id不能为空");
// ------ 0前置检查如果此账号已被封禁.
if(isDisable(id)) {
// 如果此账号已被封禁
throw new DisableLoginException(loginType, id, getDisableTime(id));
}
@ -322,32 +322,8 @@ public class StpLogic {
SaTokenConfig config = getConfig();
loginModel.build(config);
// ------ 2生成一个token
String tokenValue = null;
// --- 如果允许并发登录
if(config.getIsConcurrent()) {
// 如果配置为共享token, 则尝试从Session签名记录里取出token
if(getConfigOfIsShare()) {
// 为确保 jwt-simple 模式的 token Extra 数据生成不受旧token影响这里必须确保 is-share 配置项在 ExtraData 为空时才可以生效
// login 时提供了 Extra 数据后即使配置了 is-share=true 也不能复用旧 Token必须创建新 Token
if(loginModel.getExtraData() == null || loginModel.getExtraData().size() == 0) {
tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
}
} else {
//
}
} else {
// --- 如果不允许并发登录则将这个账号的历史登录标记为被顶下线
replaced(id, loginModel.getDevice());
}
// 如果至此仍未成功创建tokenValue, 则开始生成一个
if(tokenValue == null) {
if(SaFoxUtil.isEmpty(loginModel.getToken())) {
tokenValue = createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData());
} else {
tokenValue = loginModel.getToken();
}
}
// ------ 2分配一个可用的 Token
String tokenValue = distUsableToken(id, loginModel);
// ------ 3. 获取 User-Session , 续期
SaSession session = getSessionByLoginId(id, true);
@ -375,6 +351,47 @@ public class StpLogic {
return tokenValue;
}
/**
* 为指定账号id的登录操作分配一个可用的 Token
* @param id 账号id
* @param loginModel 此次登录的参数Model
* @return 返回 Token
*/
protected String distUsableToken(Object id, SaLoginModel loginModel) {
// 获取全局配置
Boolean isConcurrent = getConfig().getIsConcurrent();
// 如果配置为不允许并发登录则先将这个账号的历史登录标记为被顶下线
if(isConcurrent == false) {
replaced(id, loginModel.getDevice());
}
// 如果调用者预定了Token则直接返回这个预定的
if(SaFoxUtil.isNotEmpty(loginModel.getToken())) {
return loginModel.getToken();
}
// 只有在配置为 [允许并发登录] 才尝试复用旧 Token这样可以避免不必须的查询节省开销
if(isConcurrent) {
// 全局配置是否允许复用旧 Token
if(getConfigOfIsShare()) {
// 为确保 jwt-simple 模式的 token Extra 数据生成不受旧token影响这里必须确保 is-share 配置项在 ExtraData 为空时才可以生效
// login 时提供了 Extra 数据后即使配置了 is-share=true 也不能复用旧 Token必须创建新 Token
if(loginModel.isSetExtraData() == false) {
String tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
// 复用成功的话就直接返回否则还是要继续新建Token
if(SaFoxUtil.isNotEmpty(tokenValue)) {
return tokenValue;
}
}
}
}
// 如果代码走到此处说明未能成功复用旧Token需要新建Token
return createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData());
}
// --- 注销
/**

View File

@ -2,6 +2,7 @@ package cn.dev33.satoken.springboot;
import java.util.List;
import org.apache.tomcat.util.descriptor.web.LoginConfig;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
@ -15,8 +16,11 @@ import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.exception.NotSafeException;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.SaSessionCustomUtil;
import cn.dev33.satoken.stp.SaLoginConfig;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
@ -39,6 +43,7 @@ public class BasicsTest {
@BeforeAll
public static void beforeClass() {
System.out.println("\n\n------------------------ 基础测试 star ...");
SaManager.getConfig().setActivityTimeout(180);
}
// 结束
@ -53,6 +58,7 @@ public class BasicsTest {
// 基本API
Assertions.assertEquals(StpUtil.getLoginType(), "login");
Assertions.assertEquals(StpUtil.getStpLogic(), SaManager.getStpLogic("login"));
Assertions.assertEquals(StpUtil.getTokenName(), "satoken");
// 安全的更新 StpUtil StpLogic 对象
StpLogic loginStpLogic = new StpLogic("login");
@ -63,17 +69,32 @@ public class BasicsTest {
// 测试登录
@Test
public void doLogin() {
public void testDoLogin() {
// 登录
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
// token 存在
Assertions.assertNotNull(token);
Assertions.assertEquals(token, StpUtil.getTokenValueNotCut());
Assertions.assertEquals(token, StpUtil.getTokenValueByLoginId(10001));
Assertions.assertEquals(token, StpUtil.getTokenValueByLoginId(10001, SaTokenConsts.DEFAULT_LOGIN_DEVICE));
// token 队列
List<String> tokenList = StpUtil.getTokenValueListByLoginId(10001);
List<String> tokenList2 = StpUtil.getTokenValueListByLoginId(10001, SaTokenConsts.DEFAULT_LOGIN_DEVICE);
Assertions.assertEquals(token, tokenList.get(tokenList.size() - 1));
Assertions.assertEquals(token, tokenList2.get(tokenList.size() - 1));
// API 验证
Assertions.assertTrue(StpUtil.isLogin());
Assertions.assertDoesNotThrow(() -> StpUtil.checkLogin());
Assertions.assertNotNull(token); // token不为null
Assertions.assertEquals(StpUtil.getLoginIdAsLong(), 10001); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginIdAsInt(), 10001); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginIdAsString(), "10001"); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginId(), "10001"); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginIdDefaultNull(), "10001"); // loginId=10001
Assertions.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备类型
// db数据 验证
@ -88,7 +109,7 @@ public class BasicsTest {
// 测试注销
@Test
public void logout() {
public void testLogout() {
// 登录
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
@ -100,9 +121,16 @@ public class BasicsTest {
Assertions.assertNull(StpUtil.getTokenValue());
Assertions.assertFalse(StpUtil.isLogin());
Assertions.assertNull(dao.get("satoken:login:token:" + token));
// 全部客户端注销掉
StpUtil.logout(10001);
// Session 应该被清除
SaSession session = dao.getSession("satoken:login:session:" + 10001);
Assertions.assertNull(session);
// 在调用 getLoginId() 就会抛出异常
Assertions.assertEquals(StpUtil.getLoginId("无值"), "无值");
Assertions.assertThrows(NotLoginException.class, () -> StpUtil.getLoginId());
}
// 测试Session会话
@ -111,6 +139,7 @@ public class BasicsTest {
StpUtil.login(10001);
// API 应该可以获取 Session
Assertions.assertNotNull(StpUtil.getSession());
Assertions.assertNotNull(StpUtil.getSession(false));
// db中应该存在 Session
@ -159,6 +188,9 @@ public class BasicsTest {
// 抛异常
Assertions.assertThrows(NotPermissionException.class, () -> StpUtil.checkPermission("goods-add"));
Assertions.assertThrows(NotPermissionException.class, () -> StpUtil.checkPermissionAnd("goods-add", "art-add"));
// 不抛异常
Assertions.assertDoesNotThrow(() -> StpUtil.checkPermission("user-add"));
Assertions.assertDoesNotThrow(() -> StpUtil.checkPermissionAnd("art-get", "art-add"));
Assertions.assertDoesNotThrow(() -> StpUtil.checkPermissionOr("goods-add", "art-add"));
}
@ -188,6 +220,9 @@ public class BasicsTest {
// 抛异常
Assertions.assertThrows(NotRoleException.class, () -> StpUtil.checkRole("ceo"));
Assertions.assertThrows(NotRoleException.class, () -> StpUtil.checkRoleAnd("ceo", "admin"));
// 不抛异常
Assertions.assertDoesNotThrow(() -> StpUtil.checkRole("admin"));
Assertions.assertDoesNotThrow(() -> StpUtil.checkRoleAnd("admin", "super-admin"));
Assertions.assertDoesNotThrow(() -> StpUtil.checkRoleOr("ceo", "admin"));
}
@ -216,6 +251,17 @@ public class BasicsTest {
} catch (NotLoginException e) {
Assertions.assertEquals(e.getType(), NotLoginException.INVALID_TOKEN);
}
// 根据token踢下线
StpUtil.login(10001);
StpUtil.kickoutByTokenValue(StpUtil.getTokenValue());
// 场景值应该是被踢下线
try {
StpUtil.checkLogin();
} catch (NotLoginException e) {
Assertions.assertEquals(e.getType(), NotLoginException.KICK_OUT);
}
}
// 测试根据账号id强制注销
@ -263,6 +309,11 @@ public class BasicsTest {
Assertions.assertNotNull(StpUtil.stpLogic.getTokenSession(false));
SaSession session2 = dao.getSession("satoken:login:token-session:" + token);
Assertions.assertNotNull(session2);
//
SaSession tokenSession = StpUtil.getTokenSession();
SaSession tokenSession2 = StpUtil.getTokenSessionByToken(token);
Assertions.assertEquals(tokenSession.getId(), tokenSession2.getId());
}
// 测试自定义Session
@ -313,21 +364,23 @@ public class BasicsTest {
// 测试账号封禁
@Test
public void testDisable() {
Assertions.assertThrows(DisableLoginException.class, () -> {
// 封号
StpUtil.disable(10007, 200);
Assertions.assertTrue(StpUtil.isDisable(10007));
Assertions.assertEquals(dao.get("satoken:login:disable:" + 10007), DisableLoginException.BE_VALUE);
// 解封
StpUtil.untieDisable(10007);
Assertions.assertFalse(StpUtil.isDisable(10007));
Assertions.assertEquals(dao.get("satoken:login:disable:" + 10007), null);
// 封号后登陆 (会抛出 DisableLoginException 异常)
StpUtil.disable(10007, 200);
StpUtil.login(10007);
});
// 封号
StpUtil.disable(10007, 200);
Assertions.assertTrue(StpUtil.isDisable(10007));
Assertions.assertEquals(dao.get("satoken:login:disable:" + 10007), DisableLoginException.BE_VALUE);
// 封号时间
long disableTime = StpUtil.getDisableTime(10007);
Assertions.assertTrue(disableTime <= 200 && disableTime >= 199);
// 解封
StpUtil.untieDisable(10007);
Assertions.assertFalse(StpUtil.isDisable(10007));
Assertions.assertEquals(dao.get("satoken:login:disable:" + 10007), null);
// 封号后登陆 (会抛出 DisableLoginException 异常)
StpUtil.disable(10007, 200);
Assertions.assertThrows(DisableLoginException.class, () -> StpUtil.login(10007));
}
// 测试身份切换
@ -342,6 +395,12 @@ public class BasicsTest {
StpUtil.switchTo(10044);
Assertions.assertTrue(StpUtil.isSwitch());
Assertions.assertEquals(StpUtil.getLoginIdAsLong(), 10044);
// 开始身份切换 Lambda 方式
StpUtil.switchTo(10045, () -> {
Assertions.assertTrue(StpUtil.isSwitch());
Assertions.assertEquals(StpUtil.getLoginIdAsLong(), 10045);
});
// 结束切换
StpUtil.endSwitch();
@ -358,10 +417,40 @@ public class BasicsTest {
StpUtil.login(10003);
StpUtil.login(10004);
StpUtil.login(10005);
// 查询
// 查询 Token 列表
List<String> list = StpUtil.searchTokenValue("", 0, 10, true);
Assertions.assertTrue(list.size() >= 5);
// 查询 Session 列表
List<String> list2 = StpUtil.searchSessionId("", 0, 10, true);
Assertions.assertTrue(list2.size() >= 5);
list2.stream().forEach(sessionId -> {
Assertions.assertNotNull(StpUtil.getSessionBySessionId(sessionId));
});
}
// 测试会话管理(Token-Session)
@Test
public void testSearchTokenSession() {
// 登录
StpUtil.login(10001);
StpUtil.getTokenSession();
StpUtil.login(10002);
StpUtil.getTokenSession();
StpUtil.login(10003);
StpUtil.getTokenSession();
StpUtil.login(10004);
StpUtil.getTokenSession();
StpUtil.login(10005);
StpUtil.getTokenSession();
// 查询 Token-Session 列表
List<String> list2 = StpUtil.searchTokenSessionId("", 0, 10, true);
Assertions.assertTrue(list2.size() >= 5);
list2.stream().forEach(sessionId -> {
Assertions.assertNotNull(StpUtil.getSessionBySessionId(sessionId));
});
}
// 测试二级认证
@ -375,6 +464,7 @@ public class BasicsTest {
StpUtil.openSafe(2);
Assertions.assertTrue(StpUtil.isSafe());
Assertions.assertTrue(StpUtil.getSafeTime() > 0);
StpUtil.checkSafe();
// 自然结束
// Thread.sleep(2500);
@ -384,6 +474,149 @@ public class BasicsTest {
// StpUtil.openSafe(2);
StpUtil.closeSafe();
Assertions.assertFalse(StpUtil.isSafe());
// 抛异常
Assertions.assertThrows(NotSafeException.class, () -> StpUtil.checkSafe());
}
// ------------- 复杂点的
// 测试指定设备登录
@Test
public void testDoLoginByDevice() {
StpUtil.login(10001, "PC");
Assertions.assertEquals(StpUtil.getLoginDevice(), "PC");
// 指定一个其它的设备注销应该注销不掉
StpUtil.logout(10001, "APP");
Assertions.assertTrue(StpUtil.isLogin());
// 指定当前设备踢掉则能够踢掉
StpUtil.kickout(10001, "PC");
Assertions.assertFalse(StpUtil.isLogin());
// 顶掉
StpUtil.login(10001, "PC");
StpUtil.replaced(10001, "PC");
Assertions.assertFalse(StpUtil.isLogin());
try {
StpUtil.checkLogin();
} catch (NotLoginException e) {
// 场景值应该为-4
Assertions.assertEquals(e.getType(), NotLoginException.BE_REPLACED);
}
}
// 测试指定 timeout 登录
@Test
public void testDoLoginByTimeout() {
// 指定timeout 登录
StpUtil.login(10001, 100);
long timeout = StpUtil.getTokenTimeout();
Assertions.assertTrue(timeout <= 100 && timeout >= 99);
// 续期一下
StpUtil.renewTimeout(200);
timeout = StpUtil.getTokenTimeout();
Assertions.assertTrue(timeout <= 200 && timeout >= 199);
// 续期一下
StpUtil.renewTimeout(StpUtil.getTokenValue(), 300);
timeout = StpUtil.getTokenTimeout();
Assertions.assertTrue(timeout <= 300 && timeout >= 299);
// Session 也会续期
timeout = StpUtil.getSessionTimeout();
Assertions.assertTrue(timeout >= 299);
StpUtil.getTokenSession();
timeout = StpUtil.getTokenSessionTimeout();
Assertions.assertTrue(timeout >= 299);
// 注销后就是-2
StpUtil.logout();
timeout = StpUtil.getTokenTimeout();
Assertions.assertTrue(timeout == SaTokenDao.NOT_VALUE_EXPIRE);
}
// 测试预定 Token 登录
@Test
public void testDoLoginBySetToken() {
// 预定 Token 登录
StpUtil.login(10001, new SaLoginModel().setToken("qwer-qwer-qwer-qwer"));
Assertions.assertEquals(StpUtil.getTokenValue(), "qwer-qwer-qwer-qwer");
// 注销后应该清除Token
StpUtil.logout();
Assertions.assertNull(StpUtil.getTokenValue());
}
// 测试无上下文注入的登录
@Test
public void testCreateLoginSession() {
// 无上下文注入的登录
StpUtil.createLoginSession(10001);
Assertions.assertNull(StpUtil.getTokenValue());
// 无上下文注入的登录
String token = StpUtil.createLoginSession(10001, new SaLoginModel());
Assertions.assertNull(StpUtil.getTokenValue());
// 手动写入
StpUtil.setTokenValue(token);
Assertions.assertNotNull(StpUtil.getTokenValue());
// 手动写入到Cookie
StpUtil.setTokenValue(token, 10);
Assertions.assertNotNull(StpUtil.getTokenValue());
}
// 测试匿名 Token-Session
@Test
public void testAnonTokenSession() {
// token 不存在
StpUtil.logout();
Assertions.assertNull(StpUtil.getTokenValue());
// token 存在
SaSession anonTokenSession = StpUtil.getAnonTokenSession();
String token = StpUtil.getTokenValue();
Assertions.assertNotNull(token);
// 写个值
anonTokenSession.set("code", "123456");
// 登录时预定上
StpUtil.login(10001, SaLoginConfig.setToken(token));
// token不变
Assertions.assertEquals(token, StpUtil.getTokenValue());
// Token-Session 存在且不变
SaSession tokenSession = StpUtil.getTokenSession();
Assertions.assertEquals(anonTokenSession.getId(), tokenSession.getId());
// 刚才写的值仍然在
Assertions.assertEquals(tokenSession.get("code"), "123456");
}
// 测试临时过期
@Test
public void testActivityTimeout() {
// 登录
StpUtil.login(10001);
Assertions.assertNotNull(StpUtil.getTokenValue());
// 默认跟随全局 timeout
StpUtil.updateLastActivityToNow();
long activityTimeout = StpUtil.getTokenActivityTimeout();
Assertions.assertTrue(activityTimeout <=180 || activityTimeout >=179);
// 不会抛出异常
Assertions.assertDoesNotThrow(() -> StpUtil.checkActivityTimeout());
}
}