diff --git a/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java b/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java index 268e248c..4422fe1d 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/SaTokenDemoApplication.java @@ -10,8 +10,7 @@ public class SaTokenDemoApplication { public static void main(String[] args) { SpringApplication.run(SaTokenDemoApplication.class, args); - System.out.println("启动成功:sa-token配置如下:" + SaTokenManager.getConfig()); + System.out.println("\n启动成功:sa-token配置如下:" + SaTokenManager.getConfig()); } - } \ No newline at end of file diff --git a/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenDaoRedis.java b/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenDaoRedis.java index fac5b4b5..52c1f01d 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenDaoRedis.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenDaoRedis.java @@ -7,7 +7,6 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.stereotype.Component; import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.session.SaSession; @@ -15,7 +14,7 @@ import cn.dev33.satoken.session.SaSession; /** * sa-token持久层的实现类 , 基于redis */ -@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token与redis的集成 +//@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token与redis的集成 public class SaTokenDaoRedis implements SaTokenDao { @@ -58,12 +57,13 @@ public class SaTokenDaoRedis implements SaTokenDao { if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { // -2 = 无此键 return; } - stringRedisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS); +// stringRedisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS); + this.setValue(key, value, expire); } // 删除一个指定的key @Override - public void delKey(String key) { + public void deleteKey(String key) { stringRedisTemplate.delete(key); } @@ -99,7 +99,8 @@ public class SaTokenDaoRedis implements SaTokenDao { if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { // -2 = 无此键 return; } - redisTemplate.opsForValue().set(session.getId(), session, expire, TimeUnit.SECONDS); +// redisTemplate.opsForValue().set(session.getId(), session, expire, TimeUnit.SECONDS); + this.saveSession(session, expire); } // 删除一个指定的session @@ -108,11 +109,7 @@ public class SaTokenDaoRedis implements SaTokenDao { redisTemplate.delete(sessionId); } - - // 获取指定SaSession的剩余存活时间 (单位: 秒) - - @Override public long getSessionTimeout(String sessionId) { return redisTemplate.getExpire(sessionId); diff --git a/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpUserUtil.java b/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpUserUtil.java index 759f544d..6400b0d2 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpUserUtil.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpUserUtil.java @@ -1,29 +1,19 @@ package com.pj.satoken; -import java.util.Map; - -import org.springframework.stereotype.Service; - -import cn.dev33.satoken.SaTokenManager; import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpLogic; /** * 一个默认的实现 * @author kong */ -@Service public class StpUserUtil { /** * 底层的 StpLogic 对象 */ - public static StpLogic stpLogic = new StpLogic("user") { - @Override - public String getKeyTokenName() { - return SaTokenManager.getConfig().getTokenName() + "-user"; - } - }; + public static StpLogic stpLogic = new StpLogic("user"); // =================== 获取token 相关 =================== @@ -45,19 +35,27 @@ public class StpUserUtil { } /** - * 获取指定id的tokenValue + * 获取指定id的tokenValue * @param loginId . * @return */ public static String getTokenValueByLoginId(Object loginId) { return stpLogic.getTokenValueByLoginId(loginId); } - + /** - * 获取当前会话的token信息:tokenName与tokenValue - * @return 一个Map对象 + * 获取当前StpLogin的loginKey + * @return 当前StpLogin的loginKey */ - public static Map getTokenInfo() { + public static String getLoginKey(){ + return stpLogic.getLoginKey(); + } + + /** + * 获取当前会话的token信息 + * @return token信息 + */ + public static SaTokenInfo getTokenInfo() { return stpLogic.getTokenInfo(); } @@ -79,13 +77,21 @@ public class StpUserUtil { } /** - * 指定loginId的会话注销登录(踢人下线) + * 指定loginId的会话注销登录(清退下线) * @param loginId 账号id */ public static void logoutByLoginId(Object loginId) { stpLogic.logoutByLoginId(loginId); } + /** + * 指定loginId的会话注销登录(踢人下线) + * @param loginId 账号id + */ + public static void kickoutByLoginId(Object loginId) { + stpLogic.kickoutByLoginId(loginId); + } + // 查询相关 /** @@ -160,6 +166,7 @@ public class StpUserUtil { return stpLogic.getLoginIdByToken(tokenValue); } + // =================== session相关 =================== /** @@ -189,6 +196,46 @@ public class StpUserUtil { return stpLogic.getSession(); } + + + // =================== 过期时间相关 =================== + + /** + * 获取当前登录者的token剩余有效时间 (单位: 秒) + * @return token剩余有效时间 + */ + public long getTimeout() { + return stpLogic.getTokenTimeout(); + } + + /** + * 获取指定loginId的token剩余有效时间 (单位: 秒) + * @param loginId 指定loginId + * @return token剩余有效时间 + */ + public long getTimeoutByLoginId(Object loginId) { + return stpLogic.getTokenTimeoutByLoginId(loginId); + } + + /** + * 获取当前登录者的Session剩余有效时间 (单位: 秒) + * @return token剩余有效时间 + */ + public long getSessionTimeout() { + return stpLogic.getSessionTimeout(); + } + + /** + * 获取指定loginId的Session剩余有效时间 (单位: 秒) + * @param loginId 指定loginId + * @return token剩余有效时间 + */ + public long getSessionTimeoutByLoginId(Object loginId) { + return stpLogic.getSessionTimeoutByLoginId(loginId); + } + + + // =================== 权限验证操作 =================== /** diff --git a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java index 6821cc5b..28f7a485 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RestController; import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.session.SaSessionCustomUtil; +import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpUtil; /** @@ -19,15 +20,6 @@ import cn.dev33.satoken.stp.StpUtil; public class TestController { - // 当前是否登录 , 浏览器访问: http://localhost:8081/test/isLogin - @RequestMapping("isLogin") - public AjaxJson isLogin() { - System.out.println("当前是否登录:" + StpUtil.isLogin()); - System.out.println("当前登录账号id:" + StpUtil.getLoginId(-1)); - return AjaxJson.getSuccessData(StpUtil.getTokenInfo()); - } - - // 测试登录接口, 浏览器访问: http://localhost:8081/test/login @RequestMapping("login") public AjaxJson login(@RequestParam(defaultValue="10001") String id) { @@ -107,8 +99,9 @@ public class TestController { @RequestMapping("tokenInfo") public AjaxJson tokenInfo() { System.out.println("======================= 进入方法,打印当前token信息 ========================= "); - System.out.println(StpUtil.getTokenInfo()); - return AjaxJson.getSuccess(); + SaTokenInfo tokenInfo = StpUtil.getTokenInfo(); + System.out.println(tokenInfo); + return AjaxJson.getSuccessData(tokenInfo); } @@ -122,15 +115,22 @@ public class TestController { return AjaxJson.getSuccess(); } - // 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/getInfo + // 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/atLogin @SaCheckLogin // 注解式鉴权:当前会话必须登录才能通过 - @RequestMapping("getInfo") - public AjaxJson getInfo() { + @RequestMapping("atLogin") + public AjaxJson atLogin() { return AjaxJson.getSuccessData("用户信息"); } - + // [活动时间] 续签: http://localhost:8081/test/rene + @RequestMapping("rene") + public AjaxJson rene() { + StpUtil.checkActivityTimeout(); + StpUtil.updateLastActivityToNow(); + return AjaxJson.getSuccess("续签成功"); + } + // 测试踢人下线 浏览器访问: http://localhost:8081/test/kickOut @RequestMapping("kickOut") diff --git a/sa-token-demo-springboot/src/main/resources/application.yml b/sa-token-demo-springboot/src/main/resources/application.yml index 511bc472..c88224f6 100644 --- a/sa-token-demo-springboot/src/main/resources/application.yml +++ b/sa-token-demo-springboot/src/main/resources/application.yml @@ -8,8 +8,9 @@ spring: # token名称 (同时也是cookie名称) token-name: satoken # token有效期,单位s 默认30天, -1代表永不过期 -# timeout: 2592000 - timeout: -1 + timeout: 2592000 + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒, 默认-1 代表不限制 (例如可以设置为1800代表30分钟内无操作就过期) + activity-timeout: -1 # 在多人登录同一账号时,是否共享会话 (为true时共用一个,为false时新登录挤掉旧登录) is-share: true # 是否尝试从请求体里读取token diff --git a/sp-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java b/sp-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java index 805e3b4e..d9d23f76 100644 --- a/sp-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java +++ b/sp-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java @@ -9,6 +9,7 @@ public class SaTokenConfig { private String tokenName = "satoken"; // token名称 (同时也是cookie名称) private long timeout = 30 * 24 * 60 * 60; // token有效期,单位s 默认30天 + private long activityTimeout = -1; // token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒, 默认-1 代表不限制 (例如可以设置为1800代表30分钟内无操作就过期) private Boolean isShare = true; // 在多人登录同一账号时,是否共享会话 (为true时共用一个,为false时新登录挤掉旧登录) private Boolean isReadBody = true; // 是否尝试从请求体里读取token private Boolean isReadHead = true; // 是否尝试从header里读取token @@ -47,7 +48,21 @@ public class SaTokenConfig { public void setTimeout(long timeout) { this.timeout = timeout; } + + /** + * @return activityTimeout + */ + public long getActivityTimeout() { + return activityTimeout; + } + /** + * @param activityTimeout 要设置的 activityTimeout + */ + public void setActivityTimeout(long activityTimeout) { + this.activityTimeout = activityTimeout; + } + /** * @return isShare */ @@ -137,9 +152,9 @@ public class SaTokenConfig { @Override public String toString() { - return "SaTokenConfig [tokenName=" + tokenName + ", timeout=" + timeout + ", isShare=" + isShare - + ", isReadBody=" + isReadBody + ", isReadHead=" + isReadHead + ", isReadCookie=" + isReadCookie - + ", tokenStyle=" + tokenStyle + ", isV=" + isV + "]"; + return "SaTokenConfig [tokenName=" + tokenName + ", timeout=" + timeout + ", activityTimeout=" + activityTimeout + + ", isShare=" + isShare + ", isReadBody=" + isReadBody + ", isReadHead=" + isReadHead + + ", isReadCookie=" + isReadCookie + ", tokenStyle=" + tokenStyle + ", isV=" + isV + "]"; } @@ -149,6 +164,10 @@ public class SaTokenConfig { + + + + diff --git a/sp-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java b/sp-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java index eb6503f9..1a1b77dd 100644 --- a/sp-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java +++ b/sp-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java @@ -44,7 +44,7 @@ public interface SaTokenDao { * 删除一个指定的key * @param key 键名称 */ - public void delKey(String key); + public void deleteKey(String key); /** * 获取指定key的剩余存活时间 (单位: 秒) diff --git a/sp-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java b/sp-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java index 51eaf6a9..fee7682c 100644 --- a/sp-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java +++ b/sp-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java @@ -15,12 +15,12 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { /** * 所有数据集合 */ - Map dataMap = new HashMap(); + public Map dataMap = new HashMap(); /** * 过期时间集合 (单位: 毫秒) , 记录所有key的到期时间 [注意不是剩余存活时间] */ - Map expireMap = new HashMap(); + public Map expireMap = new HashMap(); @Override @@ -37,12 +37,16 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { @Override public void updateValue(String key, String value) { + if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } dataMap.put(key, value); } @Override - public void delKey(String key) { + public void deleteKey(String key) { dataMap.remove(key); + expireMap.remove(key); } @Override @@ -65,12 +69,16 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { @Override public void updateSession(SaSession session) { + if(getKeyTimeout(session.getId()) == SaTokenDao.NOT_VALUE_EXPIRE) { + return; + } // 无动作 } @Override public void deleteSession(String sessionId) { dataMap.remove(sessionId); + expireMap.remove(sessionId); } @Override @@ -99,7 +107,6 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { /** * 获取指定key的剩余存活时间 (单位:秒) */ - long getKeyTimeout(String key) { // 先检查是否已经过期 clearKeyByTimeout(key); @@ -113,8 +120,15 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { if(expire == SaTokenDao.NEVER_EXPIRE) { return SaTokenDao.NEVER_EXPIRE; } - // 计算剩余时间并返回 - return (expire - System.currentTimeMillis()) / 1000; + // ---- 计算剩余时间并返回 + long timeout = (expire - System.currentTimeMillis()) / 1000; + // 小于零时,视为不存在 + if(timeout < 0) { + dataMap.remove(key); + expireMap.remove(key); + return SaTokenDao.NOT_VALUE_EXPIRE; + } + return timeout; } diff --git a/sp-token-core/src/main/java/cn/dev33/satoken/stp/SaTokenInfo.java b/sp-token-core/src/main/java/cn/dev33/satoken/stp/SaTokenInfo.java new file mode 100644 index 00000000..c75819be --- /dev/null +++ b/sp-token-core/src/main/java/cn/dev33/satoken/stp/SaTokenInfo.java @@ -0,0 +1,179 @@ +package cn.dev33.satoken.stp; + +/** + * 用来描述一个token常用信息的类 + * @author kong + * + */ +public class SaTokenInfo { + + /** token名称 */ + public String tokenName; + + /** token值 */ + public String tokenValue; + + /** 当前是否已经登录 */ + public Boolean isLogin; + + /** 当前loginId,未登录时为null */ + public Object loginId; + + /** 当前loginKey */ + public String loginKey; + + /** token剩余有效期 (单位: 秒) */ + public long tokenTimeout; + + /** session剩余有效时间 (单位: 秒) */ + public long sessionTimeout; + + /** + * token剩余无操作有效时间 + */ + public long tokenActivityTimeout; + + + /** + * @return tokenName + */ + public String getTokenName() { + return tokenName; + } + + /** + * @param tokenName 要设置的 tokenName + */ + public SaTokenInfo setTokenName(String tokenName) { + this.tokenName = tokenName; + return this; + } + + /** + * @return tokenValue + */ + public String getTokenValue() { + return tokenValue; + } + + /** + * @param tokenValue 要设置的 tokenValue + */ + public SaTokenInfo setTokenValue(String tokenValue) { + this.tokenValue = tokenValue; + return this; + } + + /** + * @return isLogin + */ + public Boolean getIsLogin() { + return isLogin; + } + + /** + * @param isLogin 要设置的 isLogin + */ + public SaTokenInfo setIsLogin(Boolean isLogin) { + this.isLogin = isLogin; + return this; + } + + /** + * @return loginId + */ + public Object getLoginId() { + return loginId; + } + + /** + * @param loginId 要设置的 loginId + */ + public SaTokenInfo setLoginId(Object loginId) { + this.loginId = loginId; + return this; + } + + /** + * @return loginKey + */ + public String getLoginKey() { + return loginKey; + } + + /** + * @param loginKey 要设置的 loginKey + */ + public SaTokenInfo setLoginKey(String loginKey) { + this.loginKey = loginKey; + return this; + } + + /** + * @return tokenTimeout + */ + public long getTokenTimeout() { + return tokenTimeout; + } + + /** + * @param tokenTimeout 要设置的 tokenTimeout + */ + public SaTokenInfo setTokenTimeout(long tokenTimeout) { + this.tokenTimeout = tokenTimeout; + return this; + } + + /** + * @return sessionTimeout + */ + public long getSessionTimeout() { + return sessionTimeout; + } + + /** + * @param sessionTimeout 要设置的 sessionTimeout + */ + public SaTokenInfo setSessionTimeout(long sessionTimeout) { + this.sessionTimeout = sessionTimeout; + return this; + } + + /** + * @return tokenActivityTimeout + */ + public long getTokenActivityTimeout() { + return tokenActivityTimeout; + } + + /** + * @param tokenActivityTimeout 要设置的 tokenActivityTimeout + */ + public SaTokenInfo setTokenActivityTimeout(long tokenActivityTimeout) { + this.tokenActivityTimeout = tokenActivityTimeout; + return this; + } + + + + + @Override + public String toString() { + return "SaTokenInfo [tokenName=" + tokenName + ", tokenValue=" + tokenValue + ", isLogin=" + isLogin + + ", loginId=" + loginId + ", loginKey=" + loginKey + ", tokenTimeout=" + tokenTimeout + + ", sessionTimeout=" + sessionTimeout + ", tokenActivityTimeout=" + tokenActivityTimeout + "]"; + } + + + + + + + + + + + + + +} diff --git a/sp-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sp-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 008ef89f..663f5d29 100644 --- a/sp-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sp-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -1,8 +1,6 @@ package cn.dev33.satoken.stp; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @@ -24,7 +22,7 @@ import cn.dev33.satoken.util.SaTokenInsideUtil; public class StpLogic { /** - * 持久化的key前缀,多账号体系时以此值区分,比如:login、user、admin + * 持久化的key前缀,多账号认证体系时以此值区分,比如:login、user、admin */ public String loginKey = ""; @@ -61,41 +59,34 @@ public class StpLogic { * @return 当前tokenValue */ public String getTokenValue(){ - // 0、获取相应对象 + // 0. 获取相应对象 HttpServletRequest request = SaTokenManager.getSaTokenServlet().getRequest(); SaTokenConfig config = SaTokenManager.getConfig(); String keyTokenName = getTokenName(); + String tokenValue = null; - // 1、尝试从request里读取 + // 1. 尝试从request里读取 if(request.getAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY) != null) { - return String.valueOf(request.getAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY)); + tokenValue = String.valueOf(request.getAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY)); } - // 2、尝试从请求体里面读取 - if(config.getIsReadBody() == true){ - String tokenValue = request.getParameter(keyTokenName); - if(tokenValue != null) { - return tokenValue; - } + // 2. 尝试从请求体里面读取 + if(tokenValue == null && config.getIsReadBody() == true){ + tokenValue = request.getParameter(keyTokenName); } - // 3、尝试从header力读取 - if(config.getIsReadHead() == true){ - String tokenValue = request.getHeader(keyTokenName); - if(tokenValue != null) { - return tokenValue; - } + // 3. 尝试从header力读取 + if(tokenValue == null && config.getIsReadHead() == true){ + tokenValue = request.getHeader(keyTokenName); } - // 4、尝试从cookie里读取 - if(config.getIsReadCookie() == true){ + // 4. 尝试从cookie里读取 + if(tokenValue == null && config.getIsReadCookie() == true){ Cookie cookie = SaTokenManager.getSaTokenCookie().getCookie(request, keyTokenName); if(cookie != null){ - String tokenValue = cookie.getValue(); - if(tokenValue != null) { - return tokenValue; - } + tokenValue = cookie.getValue(); } } - // 5、都读取不到,那算了吧还是 - return null; + + // 5. 返回 + return tokenValue; } /** @@ -108,15 +99,28 @@ public class StpLogic { } /** - * 获取当前会话的token信息:tokenName、tokenValue、timeout - * @return 一个Map对象 + * 获取当前StpLogin的loginKey + * @return 当前StpLogin的loginKey */ - public Map getTokenInfo() { - Map map = new HashMap(); - map.put("tokenName", getTokenName()); - map.put("tokenValue", getTokenValue()); - map.put("tokenTimeout", getTimeout()); - return map; + public String getLoginKey(){ + return loginKey; + } + + /** + * 获取当前会话的token信息 + * @return token信息 + */ + public SaTokenInfo getTokenInfo() { + SaTokenInfo info = new SaTokenInfo(); + info.tokenName = getTokenName(); + info.tokenValue = getTokenValue(); + info.isLogin = isLogin(); + info.loginId = getLoginIdDefaultNull(); + info.loginKey = getLoginKey(); + info.tokenTimeout = getTokenTimeout(); + info.sessionTimeout = getSessionTimeout(); + info.tokenActivityTimeout = getTokenActivityTimeout(); + return info; } @@ -140,9 +144,9 @@ public class StpLogic { } else { // 不为null, 并且配置不共享会话,则:将原来的会话标记为[被顶替] if(config.getIsShare() == false){ -// dao.delKey(getKeyTokenValue(tokenValue)); dao.updateValue(getKeyTokenValue(tokenValue), NotLoginException.BE_REPLACED); - tokenValue = randomTokenValue(loginId); + clearLastActivity(tokenValue); // 同时清理掉[最后操作时间] + tokenValue = randomTokenValue(loginId); // 再重新生成一个token } } @@ -150,6 +154,7 @@ public class StpLogic { dao.setValue(getKeyTokenValue(tokenValue), String.valueOf(loginId), config.getTimeout()); // token -> uid dao.setValue(getKeyLoginId(loginId), tokenValue, config.getTimeout()); // uid -> token request.setAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY, tokenValue); // 保存到本次request里 + setLastActivityToNow(tokenValue); // 写入 [最后操作时间] if(config.getIsReadCookie() == true){ SaTokenManager.getSaTokenCookie().addCookie(SaTokenManager.getSaTokenServlet().getResponse(), getTokenName(), tokenValue, "/", (int)config.getTimeout()); // cookie注入 } @@ -183,7 +188,7 @@ public class StpLogic { } /** - * 指定loginId的会话注销登录(清退下线) + * 指定loginId的会话注销登录(正常注销下线) * @param loginId 账号id */ public void logoutByLoginId(Object loginId) { @@ -195,9 +200,10 @@ public class StpLogic { } // 清除相关数据 - SaTokenManager.getSaTokenDao().delKey(getKeyTokenValue(tokenValue)); // 清除token-id键值对 - SaTokenManager.getSaTokenDao().delKey(getKeyLoginId(loginId)); // 清除id-token键值对 + SaTokenManager.getSaTokenDao().deleteKey(getKeyTokenValue(tokenValue)); // 清除token-id键值对 + SaTokenManager.getSaTokenDao().deleteKey(getKeyLoginId(loginId)); // 清除id-token键值对 SaTokenManager.getSaTokenDao().deleteSession(getKeySession(loginId)); // 清除其session + clearLastActivity(tokenValue); // 同时清理掉 [最后操作时间] } /** @@ -214,8 +220,9 @@ public class StpLogic { // 清除相关数据 SaTokenManager.getSaTokenDao().updateValue(getKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); // 标记:已被踢下线 - SaTokenManager.getSaTokenDao().delKey(getKeyLoginId(loginId)); // 清除id-token键值对 + SaTokenManager.getSaTokenDao().deleteKey(getKeyLoginId(loginId)); // 清除id-token键值对 SaTokenManager.getSaTokenDao().deleteSession(getKeySession(loginId)); // 清除其session + clearLastActivity(tokenValue); // 同时清理掉 [最后操作时间] } // 查询相关 @@ -263,6 +270,9 @@ public class StpLogic { if(loginId.equals(NotLoginException.KICK_OUT)) { throw NotLoginException.newInstance(loginKey, NotLoginException.KICK_OUT); } + // 检查是否已经 [临时过期],同时更新[最后操作时间] + checkActivityTimeout(tokenValue); + updateLastActivityToNow(tokenValue); // 至此,返回loginId return loginId; } @@ -307,6 +317,10 @@ public class StpLogic { if(loginId == null || NotLoginException.ABNORMAL_LIST.contains(loginId)) { return null; } + // 如果已经[临时过期] + if(getTokenActivityTimeoutByToken(tokenValue) == SaTokenDao.NOT_VALUE_EXPIRE) { + return null; + } // 执行到此,证明loginId已经是个正常的账号id了 return loginId; } @@ -361,7 +375,7 @@ public class StpLogic { // =================== session相关 =================== /** - * 获取指定key的session, 如果没有,isCreate=是否新建并返回 + * 获取指定key的session, 如果session尚未创建,isCreate=是否新建并返回 * @param sessionId . * @param isCreate . * @return . @@ -393,15 +407,115 @@ public class StpLogic { public SaSession getSessionByLoginId(Object loginId) { return getSessionByLoginId(loginId, true); } + + /** + * 获取当前会话的session, 如果session尚未创建,isCreate=是否新建并返回 + * @param isCreate 是否新建 + * @return 当前会话的session + */ + public SaSession getSession(boolean isCreate) { + return getSessionByLoginId(getLoginId(), isCreate); + } /** - * 获取当前会话的session - * @return + * 获取当前会话的session + * @return 当前会话的session */ public SaSession getSession() { - return getSessionByLoginId(getLoginId()); + return getSession(true); } + + // =================== [临时过期] 验证相关 =================== + + /** + * 写入指定token的 [最后操作时间] 为当前时间戳 + * @param tokenValue 指定token + */ + protected void setLastActivityToNow(String tokenValue) { + // 如果token == null 或者 设置了[永不过期], 则立即返回 + if(tokenValue == null || SaTokenManager.getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) { + return; + } + // 将[最后操作时间]标记为当前时间戳 + SaTokenManager.getSaTokenDao().setValue(getKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), SaTokenManager.getConfig().getTimeout()); + } + + /** + * 清除指定token的 [最后操作时间] + * @param tokenValue 指定token + */ + protected void clearLastActivity(String tokenValue) { + // 如果token == null 或者 设置了[永不过期], 则立即返回 + if(tokenValue == null || SaTokenManager.getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) { + return; + } + // 删除[最后操作时间] + SaTokenManager.getSaTokenDao().deleteKey(getKeyLastActivityTime(tokenValue)); + // 清除标记 + SaTokenManager.getSaTokenServlet().getRequest().removeAttribute(SaTokenInsideUtil.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY); + } + + /** + * 检查指定token 是否已经[临时过期],如果已经过期则抛出异常 + * @param tokenValue 指定token + */ + public void checkActivityTimeout(String tokenValue) { + // 如果token == null 或者 设置了[永不过期], 则立即返回 + if(tokenValue == null || SaTokenManager.getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) { + return; + } + // 如果本次请求已经有了[检查标记], 则立即返回 + HttpServletRequest request = SaTokenManager.getSaTokenServlet().getRequest(); + if(request.getAttribute(SaTokenInsideUtil.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY) != null) { + return; + } + // ------------ 验证是否已经 [临时过期] + // 获取 [临时剩余时间] + long timeout = getTokenActivityTimeoutByToken(tokenValue); + // -1 代表此token已经被设置永不过期,无须继续验证 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + return; + } + // -2 代表已过期,抛出异常 + if(timeout == SaTokenDao.NOT_VALUE_EXPIRE) { + throw NotLoginException.newInstance(loginKey, NotLoginException.TOKEN_TIMEOUT); + } + // --- 至此,验证已通过 + + // 打上[检查标记],标记一下当前请求已经通过验证,避免一次请求多次验证,造成不必要的性能消耗 + request.setAttribute(SaTokenInsideUtil.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY, true); + } + + /** + * 检查当前token 是否已经[临时过期],如果已经过期则抛出异常 + */ + public void checkActivityTimeout() { + checkActivityTimeout(getTokenValue()); + } + + /** + * 续签指定token:(将 [最后操作时间] 更新为当前时间戳) + * @param tokenValue 指定token + */ + public void updateLastActivityToNow(String tokenValue) { + // 如果token == null 或者 设置了[永不过期], 则立即返回 + if(tokenValue == null || SaTokenManager.getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) { + return; + } + SaTokenManager.getSaTokenDao().updateValue(getKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis())); + } + + /** + * 续签当前token:(将 [最后操作时间] 更新为当前时间戳) + *

请注意: 即时token已经 [临时过期] 也可续签成功, + * 如果此场景下需要提示续签失败,可在此之前调用 checkActivityTimeout() 强制检查是否过期即可

+ */ + public void updateLastActivityToNow() { + updateLastActivityToNow(getTokenValue()); + } + + // =================== 过期时间相关 =================== @@ -409,7 +523,7 @@ public class StpLogic { * 获取当前登录者的token剩余有效时间 (单位: 秒) * @return token剩余有效时间 */ - public long getTimeout() { + public long getTokenTimeout() { return SaTokenManager.getSaTokenDao().getTimeout(getKeyTokenValue(getTokenValue())); } @@ -418,10 +532,68 @@ public class StpLogic { * @param loginId 指定loginId * @return token剩余有效时间 */ - public long getTimeoutByLoginId(Object loginId) { + public long getTokenTimeoutByLoginId(Object loginId) { return SaTokenManager.getSaTokenDao().getTimeout(getKeyTokenValue(getTokenValueByLoginId(loginId))); } - + + /** + * 获取当前登录者的Session剩余有效时间 (单位: 秒) + * @return token剩余有效时间 + */ + public long getSessionTimeout() { + return getSessionTimeoutByLoginId(getLoginIdDefaultNull()); + } + + /** + * 获取指定loginId的Session剩余有效时间 (单位: 秒) + * @param loginId 指定loginId + * @return token剩余有效时间 + */ + public long getSessionTimeoutByLoginId(Object loginId) { + return SaTokenManager.getSaTokenDao().getSessionTimeout(getKeySession(loginId)); + } + + /** + * 获取当前token[临时过期]剩余有效时间 (单位: 秒) + * @return token[临时过期]剩余有效时间 + */ + public long getTokenActivityTimeout() { + return getTokenActivityTimeoutByToken(getTokenValue()); + } + + /** + * 获取指定token[临时过期]剩余有效时间 (单位: 秒) + * @param tokenValue 指定token + * @return token[临时过期]剩余有效时间 + */ + public long getTokenActivityTimeoutByToken(String tokenValue) { + // 如果token为null , 则返回 -2 + if(tokenValue == null) { + return SaTokenDao.NOT_VALUE_EXPIRE; + } + // 如果设置了永不过期, 则返回 -1 + if(SaTokenManager.getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) { + return SaTokenDao.NEVER_EXPIRE; + } + // ------ 开始查询 + // 获取相关数据 + String keyLastActivityTime = getKeyLastActivityTime(tokenValue); + String lastActivityTimeString = SaTokenManager.getSaTokenDao().getValue(keyLastActivityTime); + // 查不到,返回-2 + if(lastActivityTimeString == null) { + return SaTokenDao.NOT_VALUE_EXPIRE; + } + // 计算相差时间 + long lastActivityTime = Long.valueOf(lastActivityTimeString); + long apartSecond = (System.currentTimeMillis() - lastActivityTime) / 1000; + long timeout = SaTokenManager.getConfig().getActivityTimeout() - apartSecond; + // 如果 < 0, 代表已经过期 ,返回-2 + if(timeout < 0) { + return SaTokenDao.NOT_VALUE_EXPIRE; + } + return timeout; + } + // =================== 权限验证操作 =================== @@ -520,6 +692,14 @@ public class StpLogic { public String getKeySession(Object loginId) { return SaTokenManager.getConfig().getTokenName() + ":" + loginKey + ":session:" + loginId; } + /** + * 获取key: 指定token的最后操作时间 持久化 + * @param loginId . + * @return . + */ + public String getKeyLastActivityTime(String tokenValue) { + return SaTokenManager.getConfig().getTokenName() + ":" + loginKey + ":last-activity:" + tokenValue; + } } diff --git a/sp-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java b/sp-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java index 3838e845..57604b93 100644 --- a/sp-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java +++ b/sp-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java @@ -1,7 +1,5 @@ package cn.dev33.satoken.stp; -import java.util.Map; - import cn.dev33.satoken.session.SaSession; /** @@ -35,19 +33,27 @@ public class StpUtil { } /** - * 获取指定id的tokenValue + * 获取指定id的tokenValue * @param loginId . * @return */ public static String getTokenValueByLoginId(Object loginId) { return stpLogic.getTokenValueByLoginId(loginId); } - + /** - * 获取当前会话的token信息:tokenName、tokenValue、timeout - * @return 一个Map对象 + * 获取当前StpLogin的loginKey + * @return 当前StpLogin的loginKey */ - public static Map getTokenInfo() { + public static String getLoginKey(){ + return stpLogic.getLoginKey(); + } + + /** + * 获取当前会话的token信息 + * @return token信息 + */ + public static SaTokenInfo getTokenInfo() { return stpLogic.getTokenInfo(); } @@ -69,7 +75,7 @@ public class StpUtil { } /** - * 指定loginId的会话注销登录(清退下线) + * 指定loginId的会话注销登录(正常注销下线) * @param loginId 账号id */ public static void logoutByLoginId(Object loginId) { @@ -180,6 +186,15 @@ public class StpUtil { return stpLogic.getSessionByLoginId(loginId); } + /** + * 获取当前会话的session, 如果没有,isCreate=是否新建并返回 + * @param isCreate 是否新建 + * @return 当前会话的session + */ + public static SaSession getSession(boolean isCreate) { + return stpLogic.getSession(isCreate); + } + /** * 获取当前会话的session * @return @@ -189,6 +204,24 @@ public class StpUtil { } + // =================== [临时过期] 验证相关 =================== + + /** + * 检查当前token 是否已经[临时过期],如果已经过期则抛出异常 + */ + public static void checkActivityTimeout() { + stpLogic.checkActivityTimeout(); + } + + /** + * 续签当前token:(将 [最后操作时间] 更新为当前时间戳) + *

请注意: 即时token已经 [临时过期] 也可续签成功, + * 如果此场景下需要提示续签失败,可在此之前调用 checkActivityTimeout() 强制检查是否过期即可

+ */ + public static void updateLastActivityToNow() { + stpLogic.updateLastActivityToNow(); + } + // =================== 过期时间相关 =================== @@ -196,8 +229,8 @@ public class StpUtil { * 获取当前登录者的token剩余有效时间 (单位: 秒) * @return token剩余有效时间 */ - public long getTimeout() { - return stpLogic.getTimeout(); + public static long getTimeout() { + return stpLogic.getTokenTimeout(); } /** @@ -205,10 +238,44 @@ public class StpUtil { * @param loginId 指定loginId * @return token剩余有效时间 */ - public long getTimeoutByLoginId(Object loginId) { - return stpLogic.getTimeoutByLoginId(loginId); + public static long getTimeoutByLoginId(Object loginId) { + return stpLogic.getTokenTimeoutByLoginId(loginId); } - + + /** + * 获取当前登录者的Session剩余有效时间 (单位: 秒) + * @return token剩余有效时间 + */ + public static long getSessionTimeout() { + return stpLogic.getSessionTimeout(); + } + + /** + * 获取指定loginId的Session剩余有效时间 (单位: 秒) + * @param loginId 指定loginId + * @return token剩余有效时间 + */ + public static long getSessionTimeoutByLoginId(Object loginId) { + return stpLogic.getSessionTimeoutByLoginId(loginId); + } + + /** + * 获取当前token[临时过期]剩余有效时间 (单位: 秒) + * @return token[临时过期]剩余有效时间 + */ + public static long getTokenActivityTimeout() { + return stpLogic.getTokenActivityTimeout(); + } + + /** + * 获取指定token[临时过期]剩余有效时间 (单位: 秒) + * @param tokenValue 指定token + * @return token[临时过期]剩余有效时间 + */ + public static long getTokenActivityTimeoutByToken(String tokenValue) { + return stpLogic.getTokenActivityTimeoutByToken(tokenValue); + } + // =================== 权限验证操作 =================== diff --git a/sp-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java b/sp-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java index 447c8c53..7ca9406a 100644 --- a/sp-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java +++ b/sp-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java @@ -37,6 +37,11 @@ public class SaTokenInsideUtil { * 如果token为本次请求新创建的,则以此字符串为key存储在当前request中 JUST_CREATED_SAVE_KEY */ public static final String JUST_CREATED_SAVE_KEY = "JUST_CREATED_SAVE_KEY_"; + + /** + * 如果本次请求已经验证过[无操作过期], 则以此值存储在当前request中 TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY + */ + public static final String TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY = "TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY_"; /** * 生成指定长度的随机字符串