From 2c6e656834845c668c3c61a4aae07f613e7a554d Mon Sep 17 00:00:00 2001 From: shengzhang <2393584716@qq.com> Date: Sat, 2 Jan 2021 04:00:49 +0800 Subject: [PATCH] =?UTF-8?q?v1.8.0=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 41 +- pom.xml | 3 +- sa-token-core/pom.xml | 2 +- .../dev33/satoken/config/SaTokenConfig.java | 93 +++-- .../java/cn/dev33/satoken/dao/SaTokenDao.java | 16 +- .../satoken/dao/SaTokenDaoDefaultImpl.java | 17 +- .../cn/dev33/satoken/session/SaSession.java | 112 ++++-- .../satoken/session/SaSessionCustomUtil.java | 6 +- .../cn/dev33/satoken/session/TokenSign.java | 68 ++++ .../cn/dev33/satoken/stp/SaTokenInfo.java | 1 + .../java/cn/dev33/satoken/stp/StpLogic.java | 242 +++++++----- .../java/cn/dev33/satoken/stp/StpUtil.java | 60 +-- .../cn/dev33/satoken/util/SaTokenConsts.java | 45 +++ .../dev33/satoken/util/SaTokenInsideUtil.java | 24 +- sa-token-dao-redis-jackson/.gitignore | 12 + sa-token-dao-redis-jackson/pom.xml | 35 ++ .../satoken/dao/SaTokenDaoRedisJackson.java | 185 +++++++++ .../main/resources/META-INF/spring.factories | 1 + sa-token-dao-redis/pom.xml | 16 +- .../cn/dev33/satoken/dao/SaTokenDaoRedis.java | 56 ++- sa-token-demo-springboot/pom.xml | 19 +- .../java/com/pj/SaTokenDemoApplication.java | 2 +- .../java/com/pj/satoken/MySaTokenConfig.java | 8 +- .../main/java/com/pj/satoken/StpUserUtil.java | 354 ------------------ .../main/java/com/pj/test/TestController.java | 54 ++- .../src/main/resources/application.yml | 15 +- sa-token-doc/doc/README.md | 77 ++-- sa-token-doc/doc/_sidebar.md | 6 +- sa-token-doc/doc/index.html | 5 +- sa-token-doc/doc/lib/index.css | 5 +- sa-token-doc/doc/more/update-log.md | 23 +- sa-token-doc/doc/start/download.md | 21 +- sa-token-doc/doc/start/example.md | 20 +- sa-token-doc/doc/use/at-check.md | 45 ++- sa-token-doc/doc/use/config.md | 42 +-- sa-token-doc/doc/use/dao-extend.md | 168 ++------- sa-token-doc/doc/use/jur-auth.md | 15 +- sa-token-doc/doc/use/kick.md | 19 +- sa-token-doc/doc/use/many-account.md | 4 +- sa-token-doc/doc/use/mock-person.md | 4 +- sa-token-doc/doc/use/session.md | 77 ++-- sa-token-doc/doc/use/token-style.md | 1 + sa-token-doc/index.html | 6 +- sa-token-spring-boot-starter/pom.xml | 4 +- 45 files changed, 1120 insertions(+), 910 deletions(-) create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/session/TokenSign.java create mode 100644 sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java create mode 100644 sa-token-dao-redis-jackson/.gitignore create mode 100644 sa-token-dao-redis-jackson/pom.xml create mode 100644 sa-token-dao-redis-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedisJackson.java create mode 100644 sa-token-dao-redis-jackson/src/main/resources/META-INF/spring.factories delete mode 100644 sa-token-demo-springboot/src/main/java/com/pj/satoken/StpUserUtil.java diff --git a/.gitignore b/.gitignore index f56feec7..a96470fb 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ unpackage/ .project .factorypath +/.factorypath .idea/ \ No newline at end of file diff --git a/README.md b/README.md index 3c4b7eb2..c30805e6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@

logo

-

sa-token v1.7.0

+

sa-token v1.8.0

一个JavaWeb轻量级权限认证框架,功能全面,上手简单

- + @@ -28,21 +28,18 @@ ## ⭐ sa-token是什么? -- **sa-token是一个JavaWeb轻量级权限认证框架,其API调用非常简单,有多简单呢?以登录验证为例,你只需要:** +**sa-token是一个JavaWeb轻量级权限认证框架,其API调用非常简单,有多简单呢?以登录验证为例,你只需要:** + ``` java // 在登录时写入当前会话的账号id -StpUtil.setLoginId(10001); +StpUtil.setLoginId(10001); + +// 然后在任意需要校验登录处调用以下API --- 如果当前会话未登录,这句代码会抛出 `NotLoginException`异常 +StpUtil.checkLogin(); ``` -- **然后在任意需要验证登录权限的地方:** -``` java -// 检测是否登录 --- 如果当前会话未登录,下面这句代码会抛出 `NotLoginException`异常 -StpUtil.checkLogin(); -``` - - -- **没有复杂的封装!不要任何的配置!先写入,后鉴权!只需这两行简单的调用,即可轻松完成系统登录鉴权!** +**没有复杂的封装!不要任何的配置!只需这两行简单的调用,即可轻松完成系统登录鉴权!** ## 🔥 框架设计思想 @@ -51,6 +48,24 @@ StpUtil.checkLogin(); - 功能强大:能涵盖的功能全部涵盖,不让你用个框架还要自己给框架打各种补丁 +**如果上面的示例能够证明`sa-token`的简单,那么以下API则可以证明`sa-token`的强大** +``` java +StpUtil.setLoginId(10001); // 标记当前会话登录的账号id +StpUtil.getLoginId(); // 获取当前会话登录的账号id +StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false +StpUtil.logout(); // 当前会话注销登录 +StpUtil.logoutByLoginId(10001); // 让账号为10001的会话注销登录(踢人下线) +StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false +StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false +StpUtil.getSession(); // 获取当前账号id的Session +StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session +StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值 +``` +**sa-token的API众多,请恕此处无法为您逐一展示,更多示例请戳官方在线文档** + + + + ## 💦️️ 涵盖功能 - **登录验证** —— 轻松登录鉴权,并提供五种细分场景值 - **权限验证** —— 拦截违规调用,不同角色不同授权 @@ -59,7 +74,7 @@ StpUtil.checkLogin(); - **模拟他人账号** —— 实时操作任意用户状态数据 - **持久层扩展** —— 可集成redis、MongoDB等专业缓存中间件 - **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权 -- **无cookie模式** —— APP、小程序等前后台分离场景 +- **无Cookie模式** —— APP、小程序等前后台分离场景 - **注解式鉴权** —— 优雅的将鉴权与业务代码分离 - **花式token生成** —— 内置六种token风格,还可自定义token生成策略 - **自动续签** —— 提供两种token过期策略,灵活搭配使用,还可自动续签 diff --git a/pom.xml b/pom.xml index b5fd9e1d..d2a16569 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.dev33 sa-token-parent pom - 1.7.0 + 1.8.0 sa-token @@ -21,6 +21,7 @@ sa-token-core sa-token-spring-boot-starter sa-token-dao-redis + sa-token-dao-redis-jackson diff --git a/sa-token-core/pom.xml b/sa-token-core/pom.xml index 09f6d58f..3baf598e 100644 --- a/sa-token-core/pom.xml +++ b/sa-token-core/pom.xml @@ -7,7 +7,7 @@ cn.dev33 sa-token-parent - 1.7.0 + 1.8.0 jar diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java index 355cd73e..592d2573 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaTokenConfig.java @@ -16,7 +16,10 @@ public class SaTokenConfig { /** token临时有效期 (指定时间内无操作就视为token过期) 单位/秒, 默认-1 代表不限制 (例如可以设置为1800代表30分钟内无操作就过期) */ private long activityTimeout = -1; - /** 在多人登录同一账号时,是否共享会话 (为true时共用一个,为false时新登录挤掉旧登录) */ + /** 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) */ + private Boolean allowConcurrentLogin = true; + + /** 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) */ private Boolean isShare = true; /** 是否尝试从请求体里读取token */ @@ -34,14 +37,13 @@ public class SaTokenConfig { /** 默认dao层实现类中,每次清理过期数据间隔的时间 (单位: 秒) ,默认值30秒,设置为-1代表不启动定时清理 */ private int dataRefreshPeriod = 30; - /** 获取token专属session时是否必须登录 */ + /** 获取token专属session时是否必须登录 (如果配置为true,会在每次获取token专属session时校验是否登录) */ private Boolean tokenSessionCheckLogin = true; /** 是否在初始化配置时打印版本字符画 */ private Boolean isV = true; - /** * @return tokenName */ @@ -69,7 +71,7 @@ public class SaTokenConfig { public void setTimeout(long timeout) { this.timeout = timeout; } - + /** * @return activityTimeout */ @@ -83,7 +85,21 @@ public class SaTokenConfig { public void setActivityTimeout(long activityTimeout) { this.activityTimeout = activityTimeout; } - + + /** + * @return allowConcurrentLogin + */ + public Boolean getAllowConcurrentLogin() { + return allowConcurrentLogin; + } + + /** + * @param allowConcurrentLogin 要设置的 allowConcurrentLogin + */ + public void setAllowConcurrentLogin(Boolean allowConcurrentLogin) { + this.allowConcurrentLogin = allowConcurrentLogin; + } + /** * @return isShare */ @@ -99,19 +115,19 @@ public class SaTokenConfig { } /** - * @return isReadCookie + * @return isReadBody */ - public Boolean getIsReadCookie() { - return isReadCookie; + public Boolean getIsReadBody() { + return isReadBody; } /** - * @param isReadCookie 要设置的 isReadCookie + * @param isReadBody 要设置的 isReadBody */ - public void setIsReadCookie(Boolean isReadCookie) { - this.isReadCookie = isReadCookie; + public void setIsReadBody(Boolean isReadBody) { + this.isReadBody = isReadBody; } - + /** * @return isReadHead */ @@ -127,19 +143,19 @@ public class SaTokenConfig { } /** - * @return isReadBody + * @return isReadCookie */ - public Boolean getIsReadBody() { - return isReadBody; + public Boolean getIsReadCookie() { + return isReadCookie; } /** - * @param isReadBody 要设置的 isReadBody + * @param isReadCookie 要设置的 isReadCookie */ - public void setIsReadBody(Boolean isReadBody) { - this.isReadBody = isReadBody; + public void setIsReadCookie(Boolean isReadCookie) { + this.isReadCookie = isReadCookie; } - + /** * @return tokenStyle */ @@ -153,20 +169,6 @@ public class SaTokenConfig { public void setTokenStyle(String tokenStyle) { this.tokenStyle = tokenStyle; } - - /** - * @return isV - */ - public Boolean getIsV() { - return isV; - } - - /** - * @param isV 要设置的 isV - */ - public void setIsV(Boolean isV) { - this.isV = isV; - } /** * @return dataRefreshPeriod @@ -196,20 +198,31 @@ public class SaTokenConfig { this.tokenSessionCheckLogin = tokenSessionCheckLogin; } - - + /** + * @return isV + */ + public Boolean getIsV() { + return isV; + } /** - * 将对象转为String字符串 + * @param isV 要设置的 isV */ + public void setIsV(Boolean isV) { + this.isV = isV; + } + + + @Override public String toString() { return "SaTokenConfig [tokenName=" + tokenName + ", timeout=" + timeout + ", activityTimeout=" + activityTimeout - + ", isShare=" + isShare + ", isReadBody=" + isReadBody + ", isReadHead=" + isReadHead - + ", isReadCookie=" + isReadCookie + ", tokenStyle=" + tokenStyle + ", dataRefreshPeriod=" - + dataRefreshPeriod + ", tokenSessionCheckLogin=" + tokenSessionCheckLogin + ", isV=" + isV + "]"; + + ", allowConcurrentLogin=" + allowConcurrentLogin + ", isShare=" + isShare + ", isReadBody=" + + isReadBody + ", isReadHead=" + isReadHead + ", isReadCookie=" + isReadCookie + ", tokenStyle=" + + tokenStyle + ", dataRefreshPeriod=" + dataRefreshPeriod + ", tokenSessionCheckLogin=" + + tokenSessionCheckLogin + ", isV=" + isV + "]"; } - + diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java b/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java index 6efed977..a51a651d 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java @@ -52,6 +52,13 @@ public interface SaTokenDao { */ public long getTimeout(String key); + /** + * 修改指定key的剩余存活时间 (单位: 秒) + * @param key 指定key + * @param timeout 过期时间 + */ + public void updateTimeout(String key, long timeout); + /** * 根据指定key的Session,如果没有,则返回空 @@ -80,12 +87,19 @@ public interface SaTokenDao { public void deleteSession(String sessionId); /** - * 获取指定SaSession的剩余存活时间 (单位: 秒) + * 获取指定SaSession的剩余存活时间 (单位: 秒) * @param sessionId 指定SaSession * @return 这个SaSession的剩余存活时间 (单位: 秒) */ public long getSessionTimeout(String sessionId); + /** + * 修改指定SaSession的剩余存活时间 (单位: 秒) + * @param sessionId sessionId + * @param timeout 过期时间 + */ + public void updateSessionTimeout(String sessionId, long timeout); + diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java b/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java index dad1096e..af2fe783 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java @@ -70,6 +70,11 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { return getKeyTimeout(key); } + @Override + public void updateTimeout(String key, long timeout) { + expireMap.put(key, System.currentTimeMillis() + timeout * 1000); + } + // ------------------------ Session 读写操作 @@ -104,8 +109,13 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { return getKeyTimeout(sessionId); } + @Override + public void updateSessionTimeout(String sessionId, long timeout) { + expireMap.put(sessionId, System.currentTimeMillis() + timeout * 1000); + } + - // ------------------------ Session 读写操作 + // ------------------------ 过期时间相关操作 /** * 如果指定key已经过期,则立即清除它 @@ -193,6 +203,11 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { public void endRefreshTimer() { this.refreshTimer.cancel(); } + + + + + diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSession.java b/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSession.java index e6d4b592..958eaf0c 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSession.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSession.java @@ -1,9 +1,11 @@ package cn.dev33.satoken.session; import java.io.Serializable; -import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; import cn.dev33.satoken.SaTokenManager; @@ -17,21 +19,19 @@ public class SaSession implements Serializable { private static final long serialVersionUID = 1L; - /** - * 会话id - */ + /** 此会话的id */ private String id; - /** - * 当前会话创建时间 - */ + /** 此会话的创建时间 */ private long createTime; + /** 此会话的所有数据 */ + private Map dataMap = new ConcurrentHashMap(); + /** - * 当前会话键值对 + * 构建一个 session对象 */ - private Map dataMap; - + public SaSession() {} /** * 构建一个 session对象 @@ -39,13 +39,12 @@ public class SaSession implements Serializable { */ public SaSession(String id) { this.id = id; - this.createTime = System.currentTimeMillis(); - this.dataMap = new HashMap(); + this.createTime = System.currentTimeMillis(); } /** - * 获取会话id - * @return id + * 获取此会话id + * @return 此会话的id */ public String getId() { return id; @@ -58,6 +57,67 @@ public class SaSession implements Serializable { public long getCreateTime() { return createTime; } + + + // ----------------------- tokenSign相关 + + /** + * 本session绑定的token签名列表 + */ + private List tokenSignList = new Vector(); + + /** + * 返回token签名列表 + * @return token签名列表 + */ + public List getTokenSignList() { + return new Vector<>(tokenSignList); + } + + /** + * 查找一个token签名 + * @param tokenValue token值 + * @return 查找到的tokenSign + */ + public TokenSign getTokenSign(String tokenValue) { + for (TokenSign tokenSign : getTokenSignList()) { + if(tokenSign.getValue().equals(tokenValue)){ + return tokenSign; + } + } + return null; + } + /** + * 添加一个token签名 + * @param tokenSign token签名 + */ + public void addTokenSign(TokenSign tokenSign) { + // 如果已经存在于列表中,则无需再次添加 + for (TokenSign tokenSign2 : getTokenSignList()) { + if(tokenSign2.getValue().equals(tokenSign.getValue())){ + return; + } + } + // 添加并更新 + tokenSignList.add(tokenSign); + update(); + } + /** + * 移除一个token签名 + * @param tokenValue token名称 + */ + public void removeTokenSign(String tokenValue) { + TokenSign tokenSign = getTokenSign(tokenValue); + if(tokenSignList.remove(tokenSign)) { + update(); + } + } + + + + + + // ----------------------- 存取值 /** * 写入一个值 @@ -92,7 +152,6 @@ public class SaSession implements Serializable { return defaultValue; } - /** * 移除一个值 * @param key 要移除的值的名字 @@ -123,7 +182,7 @@ public class SaSession implements Serializable { * 返回当前session会话所有key * @return 所有值的key列表 */ - public Set getAttributeKeys() { + public Set attributeKeys() { return dataMap.keySet(); } @@ -135,6 +194,10 @@ public class SaSession implements Serializable { return dataMap; } + + + // ----------------------- 一些操作 + /** * 将这个session从持久库更新一下 */ @@ -142,12 +205,17 @@ public class SaSession implements Serializable { SaTokenManager.getSaTokenDao().updateSession(this); } - -// /** 注销会话(注销后,此session会话将不再存储服务器上) */ -// public void logout() { -// SaTokenManager.getDao().delSaSession(this.id); -// } - + /** 注销会话(注销后,此session会话将不再存储服务器上) */ + public void logout() { + SaTokenManager.getSaTokenDao().deleteSession(this.id); + } + + /** 如果这个token的tokenSign数量为零,则直接注销会话 */ + public void logoutByTokenSignCountToZero() { + if(tokenSignList.size() == 0) { + logout(); + } + } } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSessionCustomUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSessionCustomUtil.java index e061c65a..a4336720 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSessionCustomUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSessionCustomUtil.java @@ -14,7 +14,11 @@ public class SaSessionCustomUtil { */ public static String sessionKey = "custom"; - /** 组织一下key */ + /** + * 组织一下自定义session的id + * @param sessionId 会话id + * @return sessionId + */ public static String getSessionKey(String sessionId) { return SaTokenManager.getConfig().getTokenName() + ":" + sessionKey + ":session:" + sessionId; } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/session/TokenSign.java b/sa-token-core/src/main/java/cn/dev33/satoken/session/TokenSign.java new file mode 100644 index 00000000..c9f6e813 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/session/TokenSign.java @@ -0,0 +1,68 @@ +package cn.dev33.satoken.session; + +import java.io.Serializable; + +/** + * 挂在到SaSession上的token签名 + * @author kong + * + */ +public class TokenSign implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 1406115065849845073L; + + /** + * token值 + */ + private String value; + + /** + * 所在设备标识 + */ + private String device; + + + /** 构建一个 */ + public TokenSign() {} + + /** + * 构建一个 + * @param value token值 + * @param device 所在设备标识 + */ + public TokenSign(String value, String device) { + this.value = value; + this.device = device; + } + + + /** + * @return value + */ + public String getValue() { + return value; + } + + /** + * @return device + */ + public String getDevice() { + return device; + } + + + + + + @Override + public String toString() { + return "TokenSign [value=" + value + ", device=" + device + "]"; + } + + + + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaTokenInfo.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaTokenInfo.java index 17893b86..a6c7ada7 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaTokenInfo.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaTokenInfo.java @@ -162,6 +162,7 @@ public class SaTokenInfo { /** * @param tokenSessionTimeout 要设置的 tokenSessionTimeout + * @return 对象自身 */ public SaTokenInfo setTokenSessionTimeout(long tokenSessionTimeout) { this.tokenSessionTimeout = tokenSessionTimeout; diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 158c4bc2..4da0bfff 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -1,5 +1,7 @@ package cn.dev33.satoken.stp; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -13,6 +15,8 @@ import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.exception.NotPermissionException; import cn.dev33.satoken.exception.NotRoleException; import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.session.TokenSign; +import cn.dev33.satoken.util.SaTokenConsts; import cn.dev33.satoken.util.SaTokenInsideUtil; /** @@ -52,10 +56,11 @@ public class StpLogic { * @param loginId loginId * @return 生成的tokenValue */ - public String randomTokenValue(Object loginId) { - return SaTokenManager.getSaTokenAction().createToken(loginId, loginKey); + public String createTokenValue(Object loginId) { + // 去除掉所有逗号 + return SaTokenManager.getSaTokenAction().createToken(loginId, loginKey).replaceAll(",", ""); } - + /** * 获取当前tokenValue * @return 当前tokenValue @@ -68,8 +73,8 @@ public class StpLogic { String tokenValue = null; // 1. 尝试从request里读取 - if(request.getAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY) != null) { - tokenValue = String.valueOf(request.getAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY)); + if(request.getAttribute(SaTokenConsts.JUST_CREATED_SAVE_KEY) != null) { + tokenValue = String.valueOf(request.getAttribute(SaTokenConsts.JUST_CREATED_SAVE_KEY)); } // 2. 尝试从请求体里面读取 if(tokenValue == null && config.getIsReadBody() == true){ @@ -91,15 +96,6 @@ public class StpLogic { return tokenValue; } - /** - * 获取指定loginId的tokenValue - * @param loginId 账号id - * @return token值 - */ - public String getTokenValueByLoginId(Object loginId) { - return SaTokenManager.getSaTokenDao().getValue(getKeyLoginId(loginId)); - } - /** * 获取当前StpLogin的loginKey * @return 当前StpLogin的loginKey @@ -135,36 +131,64 @@ public class StpLogic { */ public void setLoginId(Object loginId) { - // 1、获取相应对象 + // ------ 0、如果当前会话已经登录上了此LoginId,则立即返回 + Object loggedId = getLoginIdDefaultNull(); + if(loggedId != null && loggedId.toString().equals(loginId.toString())) { + return; + } + + // ------ 1、获取相应对象 HttpServletRequest request = SaTokenManager.getSaTokenServlet().getRequest(); SaTokenConfig config = getConfig(); SaTokenDao dao = SaTokenManager.getSaTokenDao(); - // 2、获取tokenValue - String tokenValue = getTokenValueByLoginId(loginId); // 获取旧tokenValue - if(tokenValue == null){ // 为null则创建一个新的 - tokenValue = randomTokenValue(loginId); + // ------ 2、生成一个token + String tokenValue = null; + // --- 如果允许并发登录 + if(config.getAllowConcurrentLogin() == true) { + // 如果配置为共享token, 则尝试从session签名记录里取出token + if(config.getIsShare() == true) { + tokenValue = getTokenValueByLoginId(loginId); + } } else { - // 不为null, 并且配置不共享会话,则:将原来的会话标记为[被顶替] - if(config.getIsShare() == false){ - dao.updateValue(getKeyTokenValue(tokenValue), NotLoginException.BE_REPLACED); - clearLastActivity(tokenValue); // 同时清理掉[最后操作时间] - tokenValue = randomTokenValue(loginId); // 再重新生成一个token + // --- 如果不允许并发登录 + // 如果此时[id-session]不为null,说明此账号在其他地正在登录,现在需要先把其它地的token标记为被顶下线 + SaSession session = getSessionByLoginId(loginId, false); + if(session != null) { + List tokenValueList = getTokenValueListByLoginId(loginId); + for (String token : tokenValueList) { + dao.updateValue(getKeyTokenValue(token), NotLoginException.BE_REPLACED); // 1. 将此token 标记为已顶替 + clearLastActivity(token); // 2. 清理掉[token-最后操作时间] + session.removeTokenSign(token); // 3. 清理账号session上的token签名 + } } } - - // 3、持久化 + // 如果至此,仍未成功创建tokenValue + if(tokenValue == null) { + tokenValue = createTokenValue(loginId); + } + + // ------ 3. 获取[id-session] (如果还没有创建session, 则新建, 如果已经创建,则续期) + SaSession session = getSessionByLoginId(loginId, false); + if(session == null) { + session = getSessionByLoginId(loginId); + } else { + dao.updateSessionTimeout(session.getId(), config.getTimeout()); + } + // 在session上记录token签名 + session.addTokenSign(new TokenSign(tokenValue, SaTokenConsts.DEFAULT_LOGIN_DEVICE)); + + // ------ 4. 持久化其它数据 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注入 + request.setAttribute(SaTokenConsts.JUST_CREATED_SAVE_KEY, tokenValue); // 将token保存到本次request里 + setLastActivityToNow(tokenValue); // 写入 [最后操作时间] + if(config.getIsReadCookie() == true){ // cookie注入 + SaTokenManager.getSaTokenCookie().addCookie(SaTokenManager.getSaTokenServlet().getResponse(), getTokenName(), tokenValue, "/", (int)config.getTimeout()); } } /** - * 当前会话注销登录 + * 当前会话注销登录 */ public void logout() { // 如果连token都没有,那么无需执行任何操作 @@ -176,57 +200,63 @@ public class StpLogic { if(getConfig().getIsReadCookie() == true){ SaTokenManager.getSaTokenCookie().delCookie(SaTokenManager.getSaTokenServlet().getRequest(), SaTokenManager.getSaTokenServlet().getResponse(), getTokenName()); } - // 尝试从db中获取loginId值 - String loginId = SaTokenManager.getSaTokenDao().getValue(getKeyTokenValue(tokenValue)); - // 如果根本查不到loginId,那么也无需执行任何操作 - if(loginId == null) { - return; - } - // 如果已过期或被顶替或被挤下线,那么只删除此token即可 - if(loginId.equals(NotLoginException.TOKEN_TIMEOUT) || loginId.equals(NotLoginException.BE_REPLACED) || loginId.equals(NotLoginException.KICK_OUT)) { - return; - } - // 至此,已经是一个正常的loginId,开始三清 - logoutByLoginId(loginId); + logoutByTokenValue(tokenValue); } /** - * 指定loginId的会话注销登录(正常注销下线) + * 指定token的会话注销登录 + * @param tokenValue 指定token + */ + public void logoutByTokenValue(String tokenValue) { + // 1. 清理掉[token-最后操作时间] + clearLastActivity(tokenValue); + + // 2. 尝试清除token-id键值对 (先从db中获取loginId值,如果根本查不到loginId,那么无需继续操作 ) + String loginId = SaTokenManager.getSaTokenDao().getValue(getKeyTokenValue(tokenValue)); + if(loginId == null || NotLoginException.ABNORMAL_LIST.contains(loginId)) { + return; + } + SaTokenManager.getSaTokenDao().deleteKey(getKeyTokenValue(tokenValue)); + + // 2. 尝试清理账号session上的token签名 (如果为null或已被标记为异常, 那么无需继续执行 ) + SaSession session = getSessionByLoginId(loginId, false); + if(session == null) { + return; + } + session.removeTokenSign(tokenValue); + + // 3. 尝试注销session + session.logoutByTokenSignCountToZero(); + } + + /** + * 指定loginId的会话注销登录(踢人下线) + *

当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 * @param loginId 账号id */ public void logoutByLoginId(Object loginId) { - - // 获取相应tokenValue - String tokenValue = getTokenValueByLoginId(loginId); - if(tokenValue == null) { + // 先获取这个账号的[id-session], 如果为null,则不执行任何操作 + SaSession session = getSessionByLoginId(loginId); + if(session == null) { return; } - // 清除相关数据 - SaTokenManager.getSaTokenDao().deleteKey(getKeyTokenValue(tokenValue)); // 清除token-id键值对 - SaTokenManager.getSaTokenDao().deleteKey(getKeyLoginId(loginId)); // 清除id-token键值对 - SaTokenManager.getSaTokenDao().deleteSession(getKeySession(loginId)); // 清除其session - clearLastActivity(tokenValue); // 同时清理掉 [最后操作时间] + // 循环token签名列表,开始删除相关信息 + List tokenSignList = session.getTokenSignList(); + for (TokenSign tokenSign : tokenSignList) { + // 1. 获取token + String tokenValue = tokenSign.getValue(); + // 2. 清理掉[token-最后操作时间] + clearLastActivity(tokenValue); + // 3. 标记:已被踢下线 + SaTokenManager.getSaTokenDao().updateValue(getKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); // 标记:已被踢下线 + // 4. 清理账号session上的token签名 + session.removeTokenSign(tokenValue); + } + // 尝试注销session + session.logoutByTokenSignCountToZero(); } - /** - * 指定loginId的会话注销登录(踢人下线) - * @param loginId 账号id - */ - public void kickoutByLoginId(Object loginId) { - - // 获取相应tokenValue - String tokenValue = getTokenValueByLoginId(loginId); - if(tokenValue == null) { - return; - } - - // 清除相关数据 - SaTokenManager.getSaTokenDao().updateValue(getKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); // 标记:已被踢下线 - SaTokenManager.getSaTokenDao().deleteKey(getKeyLoginId(loginId)); // 清除id-token键值对 - SaTokenManager.getSaTokenDao().deleteSession(getKeySession(loginId)); // 清除其session - clearLastActivity(tokenValue); // 同时清理掉 [最后操作时间] - } // 查询相关 @@ -454,7 +484,6 @@ public class StpLogic { /** * 获取当前token的专属-session,如果session尚未创建,isCreate代表是否新建并返回 - *

只有当前会话属于登录状态才可调用 * @param isCreate 是否新建 * @return session会话 */ @@ -474,8 +503,7 @@ public class StpLogic { } /** - * 获取当前token的专属-session,如果session尚未创建,则新建并返回 - *

只有当前会话属于登录状态才可调用 + * 获取当前token的专属-session,如果session尚未创建,则新建并返回 * @return session会话 */ public SaSession getTokenSession() { @@ -510,7 +538,7 @@ public class StpLogic { // 删除[最后操作时间] SaTokenManager.getSaTokenDao().deleteKey(getKeyLastActivityTime(tokenValue)); // 清除标记 - SaTokenManager.getSaTokenServlet().getRequest().removeAttribute(SaTokenInsideUtil.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY); + SaTokenManager.getSaTokenServlet().getRequest().removeAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY); } /** @@ -524,7 +552,7 @@ public class StpLogic { } // 如果本次请求已经有了[检查标记], 则立即返回 HttpServletRequest request = SaTokenManager.getSaTokenServlet().getRequest(); - if(request.getAttribute(SaTokenInsideUtil.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY) != null) { + if(request.getAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY) != null) { return; } // ------------ 验证是否已经 [临时过期] @@ -541,7 +569,7 @@ public class StpLogic { // --- 至此,验证已通过 // 打上[检查标记],标记一下当前请求已经通过验证,避免一次请求多次验证,造成不必要的性能消耗 - request.setAttribute(SaTokenInsideUtil.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY, true); + request.setAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY, true); } /** @@ -795,7 +823,43 @@ public class StpLogic { throw new NotPermissionException(permissionArray[0], this.loginKey); // 没有权限抛出异常 } } - + + + // =================== id 反查token 相关操作 =================== + + + /** + * 获取指定loginId的tokenValue + *

在配置为允许并发登录时,此方法只会返回队列的最后一个token, + * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId + * @param loginId 账号id + * @return token值 + */ + public String getTokenValueByLoginId(Object loginId) { + List tokenValueList = getTokenValueListByLoginId(loginId); + return tokenValueList.size() == 0 ? null : tokenValueList.get(tokenValueList.size() - 1); + } + + /** + * 获取指定loginId的tokenValue + * @param loginId 账号id + * @return 此loginId的所有相关token + */ + public List getTokenValueListByLoginId(Object loginId) { + // 如果session为null的话直接返回空集合 + SaSession session = getSessionByLoginId(loginId, false); + if(session == null) { + return Arrays.asList(); + } + // 遍历解析 + List tokenSignList = session.getTokenSignList(); + List tokenValueList = new ArrayList<>(); + for (TokenSign tokenSign : tokenSignList) { + tokenValueList.add(tokenSign.getValue()); + } + return tokenValueList; + } + // =================== 返回相应key =================== @@ -807,21 +871,13 @@ public class StpLogic { return getConfig().getTokenName(); } /** - * 获取key: tokenValue 持久化 + * 获取key: tokenValue 持久化 token-id * @param tokenValue token值 * @return key */ public String getKeyTokenValue(String tokenValue) { return getConfig().getTokenName() + ":" + loginKey + ":token:" + tokenValue; } - /** - * 获取key: id 持久化 - * @param loginId 账号id - * @return key - */ - public String getKeyLoginId(Object loginId) { - return getConfig().getTokenName() + ":" + loginKey + ":id:" + loginId; - } /** * 获取key: session 持久化 * @param loginId 账号id @@ -846,9 +902,13 @@ public class StpLogic { public String getKeyLastActivityTime(String tokenValue) { return getConfig().getTokenName() + ":" + loginKey + ":last-activity:" + tokenValue; } - + + + // =================== Bean对象代理 =================== + /** * 返回配置对象 + * @return 配置对象 */ public SaTokenConfig getConfig() { // 为什么再次代理一层? 为某些极端业务场景下[需要不同StpLogic不同配置]提供便利 @@ -856,4 +916,8 @@ public class StpLogic { } + + + + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java index 23b6c09c..2d98ec82 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java @@ -1,5 +1,7 @@ package cn.dev33.satoken.stp; +import java.util.List; + import cn.dev33.satoken.session.SaSession; /** @@ -32,15 +34,6 @@ public class StpUtil { return stpLogic.getTokenValue(); } - /** - * 获取指定loginId的tokenValue - * @param loginId 账号id - * @return token值 - */ - public static String getTokenValueByLoginId(Object loginId) { - return stpLogic.getTokenValueByLoginId(loginId); - } - /** * 获取当前StpLogin的loginKey * @return 当前StpLogin的loginKey @@ -76,20 +69,22 @@ public class StpUtil { } /** - * 指定loginId的会话注销登录(正常注销下线) + * 指定token的会话注销登录 + * @param tokenValue 指定token + */ + public static void logoutByTokenValue(String tokenValue) { + stpLogic.logoutByTokenValue(tokenValue); + } + + /** + * 指定loginId的会话注销登录(踢人下线) + *

当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 * @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); - } // 查询相关 @@ -181,10 +176,9 @@ public class StpUtil { } /** - * 获取指定loginId的session, 如果session尚未创建,isCreate=是否新建并返回 - * @param loginId 账号id - * @param isCreate 是否新建 - * @return SaSession + * 获取指定loginId的session,如果session尚未创建,则新建并返回 + * @param loginId 账号id + * @return session会话 */ public static SaSession getSessionByLoginId(Object loginId) { return stpLogic.getSessionByLoginId(loginId); @@ -221,7 +215,6 @@ public class StpUtil { /** * 获取当前token的专属-session,如果session尚未创建,则新建并返回 - *

只有当前会话属于登录状态才可调用 * @return session会话 */ public static SaSession getTokenSession() { @@ -376,4 +369,27 @@ public class StpUtil { } + // =================== id 反查token 相关操作 =================== + + /** + * 获取指定loginId的tokenValue + *

在配置为允许并发登录时,此方法只会返回队列的最后一个token, + * 如果你需要返回此账号id的所有token,请调用 getTokenValueListByLoginId + * @param loginId 账号id + * @return token值 + */ + public static String getTokenValueByLoginId(Object loginId) { + return stpLogic.getTokenValueByLoginId(loginId); + } + + /** + * 获取指定loginId的tokenValue + * @param loginId 账号id + * @return 此loginId的所有相关token + */ + public static List getTokenValueListByLoginId(Object loginId) { + return stpLogic.getTokenValueListByLoginId(loginId); + } + + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java new file mode 100644 index 00000000..10e8decd --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenConsts.java @@ -0,0 +1,45 @@ +package cn.dev33.satoken.util; + +/** + * 定义sa-token的所有常量 + * @author kong + * + */ +public class SaTokenConsts { + + /** + * sa-token 版本号 + */ + public static final String VERSION_NO = "v1.8.0"; + + /** + * sa-token 开源地址 + */ + public static final String GITHUB_URL = "https://github.com/click33/sa-token"; + + /** + * 如果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_"; + + /** + * 在登录时,默认使用的设备名称 + */ + public static final String DEFAULT_LOGIN_DEVICE = "default-device"; + +// /** +// * 在用一个字符串存储多个token时,所使用的分隔符 +// */ +// public static final String MULTIPLE_TOKEN_SEPARATOR = ","; + + + + + + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java index b85c16f1..483a3422 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaTokenInsideUtil.java @@ -10,16 +10,6 @@ import java.util.Random; public class SaTokenInsideUtil { - /** - * sa-token 版本号 - */ - public static final String VERSION_NO = "v1.7.0"; - - /** - * sa-token 开源地址 - */ - public static final String GITHUB_URL = "https://github.com/click33/sa-token"; - /** * 打印 sa-token 版本字符画 */ @@ -28,20 +18,11 @@ public class SaTokenInsideUtil { "____ ____ ___ ____ _ _ ____ _ _ \r\n" + "[__ |__| __ | | | |_/ |___ |\\ | \r\n" + "___] | | | |__| | \\_ |___ | \\| \r\n" + - "sa-token:" + VERSION_NO + " \r\n" + - "GitHub:" + GITHUB_URL; // + "\r\n"; + "sa-token:" + SaTokenConsts.VERSION_NO + " \r\n" + + "GitHub:" + SaTokenConsts.GITHUB_URL; // + "\r\n"; System.out.println(str); } - /** - * 如果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_"; /** * 生成指定长度的随机字符串 @@ -61,6 +42,7 @@ public class SaTokenInsideUtil { /** * 以当前时间戳和随机int数字拼接一个随机字符串 + * @return 随机字符串 */ public static String getMarking28() { return System.currentTimeMillis() + "" + new Random().nextInt(Integer.MAX_VALUE); diff --git a/sa-token-dao-redis-jackson/.gitignore b/sa-token-dao-redis-jackson/.gitignore new file mode 100644 index 00000000..f56feec7 --- /dev/null +++ b/sa-token-dao-redis-jackson/.gitignore @@ -0,0 +1,12 @@ +target/ + +node_modules/ +bin/ +.settings/ +unpackage/ +.classpath +.project + +.factorypath + +.idea/ \ No newline at end of file diff --git a/sa-token-dao-redis-jackson/pom.xml b/sa-token-dao-redis-jackson/pom.xml new file mode 100644 index 00000000..293c235a --- /dev/null +++ b/sa-token-dao-redis-jackson/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + cn.dev33 + sa-token-parent + 1.8.0 + + jar + + sa-token-dao-redis-jackson + sa-token-dao-redis-jackson + sa-token integrate redis (to jackson) + + + + + cn.dev33 + sa-token-spring-boot-starter + 1.8.0 + + + + org.springframework.boot + spring-boot-starter-data-redis + 2.3.7.RELEASE + + + + + + diff --git a/sa-token-dao-redis-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedisJackson.java b/sa-token-dao-redis-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedisJackson.java new file mode 100644 index 00000000..e0af12ba --- /dev/null +++ b/sa-token-dao-redis-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedisJackson.java @@ -0,0 +1,185 @@ +package cn.dev33.satoken.dao; + +import java.lang.reflect.Field; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import cn.dev33.satoken.session.SaSession; + +/** + * sa-token持久层的实现类, 基于redis (to jackson) + */ +@Component +public class SaTokenDaoRedisJackson implements SaTokenDao { + + /** + * ObjectMapper对象 (以public作用于暴露出此对象,方便开发者二次更改配置) + */ + public ObjectMapper objectMapper; + + /** + * string专用 + */ + @Autowired + public StringRedisTemplate stringRedisTemplate; + + /** + * SaSession专用 + */ + public RedisTemplate sessionRedisTemplate; + @Autowired + public void setSessionRedisTemplate(RedisConnectionFactory connectionFactory) { + // 指定相应的序列化方案 + StringRedisSerializer keySerializer = new StringRedisSerializer(); + GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(); + // 通过反射获取Mapper对象, 配置[忽略未知字段], 增强兼容性 + try { + Field field = GenericJackson2JsonRedisSerializer.class.getDeclaredField("mapper"); + field.setAccessible(true); + ObjectMapper objectMapper = (ObjectMapper) field.get(valueSerializer); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.objectMapper = objectMapper; + } catch (Exception e) { + System.err.println(e.getMessage()); + } + // 构建RedisTemplate + RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(connectionFactory); + template.setKeySerializer(keySerializer); + template.setHashKeySerializer(keySerializer); + template.setValueSerializer(valueSerializer); + template.setHashValueSerializer(valueSerializer); + template.afterPropertiesSet(); + if(this.sessionRedisTemplate == null) { + this.sessionRedisTemplate = template; + } + } + + + /** + * 根据key获取value,如果没有,则返回空 + */ + @Override + public String getValue(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + /** + * 写入指定key-value键值对,并设定过期时间(单位:秒) + */ + @Override + public void setValue(String key, String value, long timeout) { + // 判断是否为永不过期 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + stringRedisTemplate.opsForValue().set(key, value); + } else { + stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); + } + } + + /** + * 修改指定key-value键值对 (过期时间取原来的值) + */ + @Override + public void updateValue(String key, String value) { + long expire = getTimeout(key); + if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { // -2 = 无此键 + return; + } + this.setValue(key, value, expire); + } + + /** + * 删除一个指定的key + */ + @Override + public void deleteKey(String key) { + stringRedisTemplate.delete(key); + } + + /** + * 根据key获取value,如果没有,则返回空 + */ + @Override + public long getTimeout(String key) { + return stringRedisTemplate.getExpire(key); + } + + /** + * 修改指定key的剩余存活时间 (单位: 秒) + */ + @Override + public void updateTimeout(String key, long timeout) { + stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); + } + + + + /** + * 根据指定key的Session,如果没有,则返回空 + */ + @Override + public SaSession getSession(String sessionId) { + return sessionRedisTemplate.opsForValue().get(sessionId); + } + + /** + * 将指定Session持久化 + */ + @Override + public void saveSession(SaSession session, long timeout) { + // 判断是否为永不过期 + if(timeout == SaTokenDao.NEVER_EXPIRE) { + sessionRedisTemplate.opsForValue().set(session.getId(), session); + } else { + sessionRedisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS); + } + } + + /** + * 更新指定session + */ + @Override + public void updateSession(SaSession session) { + long expire = getSessionTimeout(session.getId()); + if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { // -2 = 无此键 + return; + } + this.saveSession(session, expire); + } + + /** + * 删除一个指定的session + */ + @Override + public void deleteSession(String sessionId) { + sessionRedisTemplate.delete(sessionId); + } + + /** + * 获取指定SaSession的剩余存活时间 (单位: 秒) + */ + @Override + public long getSessionTimeout(String sessionId) { + return sessionRedisTemplate.getExpire(sessionId); + } + + /** + * 修改指定SaSession的剩余存活时间 (单位: 秒) + */ + @Override + public void updateSessionTimeout(String sessionId, long timeout) { + sessionRedisTemplate.expire(sessionId, timeout, TimeUnit.SECONDS); + } + +} diff --git a/sa-token-dao-redis-jackson/src/main/resources/META-INF/spring.factories b/sa-token-dao-redis-jackson/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..d792b9f9 --- /dev/null +++ b/sa-token-dao-redis-jackson/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.dao.SaTokenDaoRedisJackson \ No newline at end of file diff --git a/sa-token-dao-redis/pom.xml b/sa-token-dao-redis/pom.xml index 778e0297..9e66756a 100644 --- a/sa-token-dao-redis/pom.xml +++ b/sa-token-dao-redis/pom.xml @@ -7,7 +7,7 @@ cn.dev33 sa-token-parent - 1.7.0 + 1.8.0 jar @@ -20,14 +20,14 @@ cn.dev33 sa-token-spring-boot-starter - 1.7.0 + 1.8.0 + + + + org.springframework.boot + spring-boot-starter-data-redis + 2.3.7.RELEASE - - - org.springframework.boot - spring-boot-starter-redis - RELEASE - diff --git a/sa-token-dao-redis/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedis.java b/sa-token-dao-redis/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedis.java index a475eb93..81d5e0be 100644 --- a/sa-token-dao-redis/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedis.java +++ b/sa-token-dao-redis/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedis.java @@ -3,6 +3,7 @@ package cn.dev33.satoken.dao; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; @@ -21,18 +22,28 @@ public class SaTokenDaoRedis implements SaTokenDao { * string专用 */ @Autowired - StringRedisTemplate stringRedisTemplate; + public StringRedisTemplate stringRedisTemplate; /** * SaSession专用 */ - RedisTemplate redisTemplate; + public RedisTemplate sessionRedisTemplate; @Autowired - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void setRedisTemplate(RedisTemplate redisTemplate) { - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - this.redisTemplate = redisTemplate; + public void setSessionRedisTemplate(RedisConnectionFactory connectionFactory) { + // 指定相应的序列化方案 + StringRedisSerializer keySerializer = new StringRedisSerializer(); + JdkSerializationRedisSerializer valueSerializer = new JdkSerializationRedisSerializer(); + // 构建RedisTemplate + RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(connectionFactory); + template.setKeySerializer(keySerializer); + template.setHashKeySerializer(keySerializer); + template.setValueSerializer(valueSerializer); + template.setHashValueSerializer(valueSerializer); + template.afterPropertiesSet(); + if(this.sessionRedisTemplate == null) { + this.sessionRedisTemplate = template; + } } @@ -58,7 +69,7 @@ public class SaTokenDaoRedis implements SaTokenDao { } /** - * 根据key获取value,如果没有,则返回空 + * 修改指定key-value键值对 (过期时间取原来的值) */ @Override public void updateValue(String key, String value) { @@ -70,7 +81,7 @@ public class SaTokenDaoRedis implements SaTokenDao { } /** - * 根据key获取value,如果没有,则返回空 + * 删除一个指定的key */ @Override public void deleteKey(String key) { @@ -84,6 +95,15 @@ public class SaTokenDaoRedis implements SaTokenDao { public long getTimeout(String key) { return stringRedisTemplate.getExpire(key); } + + /** + * 修改指定key的剩余存活时间 (单位: 秒) + */ + @Override + public void updateTimeout(String key, long timeout) { + stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); + } + /** @@ -91,7 +111,7 @@ public class SaTokenDaoRedis implements SaTokenDao { */ @Override public SaSession getSession(String sessionId) { - return redisTemplate.opsForValue().get(sessionId); + return sessionRedisTemplate.opsForValue().get(sessionId); } /** @@ -101,9 +121,9 @@ public class SaTokenDaoRedis implements SaTokenDao { public void saveSession(SaSession session, long timeout) { // 判断是否为永不过期 if(timeout == SaTokenDao.NEVER_EXPIRE) { - redisTemplate.opsForValue().set(session.getId(), session); + sessionRedisTemplate.opsForValue().set(session.getId(), session); } else { - redisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS); + sessionRedisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS); } } @@ -124,7 +144,7 @@ public class SaTokenDaoRedis implements SaTokenDao { */ @Override public void deleteSession(String sessionId) { - redisTemplate.delete(sessionId); + sessionRedisTemplate.delete(sessionId); } /** @@ -132,7 +152,15 @@ public class SaTokenDaoRedis implements SaTokenDao { */ @Override public long getSessionTimeout(String sessionId) { - return redisTemplate.getExpire(sessionId); + return sessionRedisTemplate.getExpire(sessionId); } + /** + * 修改指定SaSession的剩余存活时间 (单位: 秒) + */ + @Override + public void updateSessionTimeout(String sessionId, long timeout) { + sessionRedisTemplate.expire(sessionId, timeout, TimeUnit.SECONDS); + } + } diff --git a/sa-token-demo-springboot/pom.xml b/sa-token-demo-springboot/pom.xml index 03008c76..bc599554 100644 --- a/sa-token-demo-springboot/pom.xml +++ b/sa-token-demo-springboot/pom.xml @@ -29,14 +29,27 @@ cn.dev33 sa-token-spring-boot-starter - 1.7.0 + 1.8.0 - + + + + + + + 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 4422fe1d..61e902b8 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 @@ -7,7 +7,7 @@ import cn.dev33.satoken.SaTokenManager; @SpringBootApplication public class SaTokenDemoApplication { - + public static void main(String[] args) { SpringApplication.run(SaTokenDemoApplication.class, args); System.out.println("\n启动成功:sa-token配置如下:" + SaTokenManager.getConfig()); diff --git a/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenConfig.java b/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenConfig.java index af334cc8..c5fc7823 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenConfig.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenConfig.java @@ -20,12 +20,10 @@ public class MySaTokenConfig implements WebMvcConfigurer { SaTokenConfig config = new SaTokenConfig(); config.setTokenName("satoken"); // token名称 (同时也是cookie名称) config.setTimeout(30 * 24 * 60 * 60); // token有效期,单位s 默认30天 - config.setIsShare(true); // 在多人登录同一账号时,是否共享会话 (为true时共用一个,为false时新登录挤掉旧登录) - config.setIsReadBody(true); // 是否尝试从请求体里读取token - config.setIsReadHead(true); // 是否尝试从header里读取token - config.setIsReadCookie(true); // 是否尝试从cookie里读取token + config.setActivityTimeout(-1); // token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + config.setAllowConcurrentLogin(true); // 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + config.setIsShare(true); // 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) config.setTokenStyle("uuid"); // token风格 - config.setIsV(true); // 是否在初始化配置时打印版本字符画 return config; } 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 deleted file mode 100644 index 4311089c..00000000 --- a/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpUserUtil.java +++ /dev/null @@ -1,354 +0,0 @@ -package com.pj.satoken; - -import cn.dev33.satoken.session.SaSession; -import cn.dev33.satoken.stp.SaTokenInfo; -import cn.dev33.satoken.stp.StpLogic; - -/** - * user认证实现 - * @author kong - */ -public class StpUserUtil { - - /** - * 底层的 StpLogic 对象 - */ - public static StpLogic stpLogic = new StpLogic("user"); - - - // =================== 获取token 相关 =================== - - /** - * 返回token名称 - * @return 此StpLogic的token名称 - */ - public static String getTokenName() { - return stpLogic.getTokenName(); - } - - /** - * 获取当前tokenValue - * @return 当前tokenValue - */ - public static String getTokenValue() { - return stpLogic.getTokenValue(); - } - - /** - * 获取指定loginId的tokenValue - * @param loginId 账号id - * @return token值 - */ - public static String getTokenValueByLoginId(Object loginId) { - return stpLogic.getTokenValueByLoginId(loginId); - } - - /** - * 获取当前StpLogin的loginKey - * @return 当前StpLogin的loginKey - */ - public static String getLoginKey(){ - return stpLogic.getLoginKey(); - } - - /** - * 获取当前会话的token信息 - * @return token信息 - */ - public static SaTokenInfo getTokenInfo() { - return stpLogic.getTokenInfo(); - } - - - // =================== 登录相关操作 =================== - - /** - * 在当前会话上登录id - * @param loginId 登录id,建议的类型:(long | int | String) - */ - public static void setLoginId(Object loginId) { - stpLogic.setLoginId(loginId); - } - - /** - * 当前会话注销登录 - */ - public static void logout() { - stpLogic.logout(); - } - - /** - * 指定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); - } - - // 查询相关 - - /** - * 获取当前会话是否已经登录 - * @return 是否已登录 - */ - public static boolean isLogin() { - return stpLogic.isLogin(); - } - - /** - * 检验当前会话是否已经登录,如未登录,则抛出异常 - */ - public static void checkLogin() { - stpLogic.checkLogin(); - } - - /** - * 获取当前会话账号id, 如果未登录,则抛出异常 - * @return 账号id - */ - public static Object getLoginId() { - return stpLogic.getLoginId(); - } - - /** - * 获取当前会话登录id, 如果未登录,则返回默认值 - * @param 返回类型 - * @param defaultValue 默认值 - * @return 登录id - */ - public static T getLoginId(T defaultValue) { - return stpLogic.getLoginId(defaultValue); - } - - /** - * 获取当前会话登录id, 如果未登录,则返回null - * @return 账号id - */ - public static Object getLoginIdDefaultNull() { - return stpLogic.getLoginIdDefaultNull(); - } - - /** - * 获取当前会话登录id, 并转换为String - * @return 账号id - */ - public static String getLoginIdAsString() { - return stpLogic.getLoginIdAsString(); - } - - /** - * 获取当前会话登录id, 并转换为int - * @return 账号id - */ - public static int getLoginIdAsInt() { - return stpLogic.getLoginIdAsInt(); - } - - /** - * 获取当前会话登录id, 并转换为long - * @return 账号id - */ - public static long getLoginIdAsLong() { - return stpLogic.getLoginIdAsLong(); - } - - /** - * 获取指定token对应的登录id,如果未登录,则返回 null - * @param tokenValue token - * @return 登录id - */ - public static Object getLoginIdByToken(String tokenValue) { - return stpLogic.getLoginIdByToken(tokenValue); - } - - - // =================== session相关 =================== - - /** - * 获取指定loginId的session, 如果session尚未创建,isCreate=是否新建并返回 - * @param loginId 账号id - * @param isCreate 是否新建 - * @return SaSession - */ - public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) { - return stpLogic.getSessionByLoginId(loginId, isCreate); - } - - /** - * 获取指定loginId的session, 如果session尚未创建,isCreate=是否新建并返回 - * @param loginId 账号id - * @param isCreate 是否新建 - * @return SaSession - */ - public static SaSession getSessionByLoginId(Object loginId) { - return stpLogic.getSessionByLoginId(loginId); - } - - /** - * 获取当前会话的session, 如果session尚未创建,isCreate=是否新建并返回 - * @param isCreate 是否新建 - * @return 当前会话的session - */ - public static SaSession getSession(boolean isCreate) { - return stpLogic.getSession(isCreate); - } - - /** - * 获取当前会话的session,如果session尚未创建,则新建并返回 - * @return 当前会话的session - */ - public static SaSession getSession() { - return stpLogic.getSession(); - } - - - // =================== token专属session =================== - - /** - * 获取指定token的专属session,如果session尚未创建,则新建并返回 - * @param tokenValue token值 - * @return session会话 - */ - public static SaSession getTokenSessionByToken(String tokenValue) { - return stpLogic.getTokenSessionByToken(tokenValue); - } - - /** - * 获取当前token的专属-session,如果session尚未创建,则新建并返回 - *

只有当前会话属于登录状态才可调用 - * @return session会话 - */ - public static SaSession getTokenSession() { - return stpLogic.getTokenSession(); - } - - - // =================== [临时过期] 验证相关 =================== - - /** - * 检查当前token 是否已经[临时过期],如果已经过期则抛出异常 - */ - public static void checkActivityTimeout() { - stpLogic.checkActivityTimeout(); - } - - /** - * 续签当前token:(将 [最后操作时间] 更新为当前时间戳) - *

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

- */ - public static void updateLastActivityToNow() { - stpLogic.updateLastActivityToNow(); - } - - - // =================== 过期时间相关 =================== - - /** - * 获取当前登录者的token剩余有效时间 (单位: 秒) - * @return token剩余有效时间 - */ - public static long getTimeout() { - return stpLogic.getTokenTimeout(); - } - - /** - * 获取指定loginId的token剩余有效时间 (单位: 秒) - * @param loginId 指定loginId - * @return token剩余有效时间 - */ - 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); - } - - - - // =================== 权限验证操作 =================== - - /** - * 指定账号id是否含有指定权限 - * @param loginId 账号id - * @param permissionCode 权限码 - * @return 是否含有指定权限 - */ - public static boolean hasPermission(Object loginId, String permissionCode) { - return stpLogic.hasPermission(loginId, permissionCode); - } - - /** - * 当前账号id是否含有指定权限 - * @param permissionCode 权限码 - * @return 是否含有指定权限 - */ - public static boolean hasPermission(String permissionCode) { - return stpLogic.hasPermission(permissionCode); - } - - /** - * 当前账号是否含有指定权限, 没有就抛出异常 - * @param permissionCode 权限码 - */ - public static void checkPermission(String permissionCode) { - stpLogic.checkPermission(permissionCode); - } - - /** - * 当前账号是否含有指定权限, [指定多个,必须全都有] - * @param permissionCodeArray 权限码数组 - */ - public static void checkPermissionAnd(String... permissionCodeArray) { - stpLogic.checkPermissionAnd(permissionCodeArray); - } - - /** - * 当前账号是否含有指定权限, [指定多个,有一个就可以通过] - * @param permissionCodeArray 权限码数组 - */ - public static void checkPermissionOr(String... permissionCodeArray) { - stpLogic.checkPermissionOr(permissionCodeArray); - } - - -} 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 661fe6b2..096ac1c4 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 @@ -1,11 +1,17 @@ package com.pj.test; +import java.util.Date; + import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; import cn.dev33.satoken.session.SaSessionCustomUtil; import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpUtil; @@ -27,22 +33,22 @@ public class TestController { System.out.println("当前会话的token:" + StpUtil.getTokenValue()); System.out.println("当前是否登录:" + StpUtil.isLogin()); System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull()); + StpUtil.setLoginId(id); // 在当前会话登录此账号 System.out.println("登录成功"); System.out.println("当前是否登录:" + StpUtil.isLogin()); System.out.println("当前登录账号:" + StpUtil.getLoginId()); System.out.println("当前登录账号:" + StpUtil.getLoginIdAsInt()); // 获取登录id并转为int +// System.out.println("当前token信息:" + StpUtil.getTokenInfo()); -// StpUtil.logout(); -// System.out.println("注销登录"); -// System.out.println("当前是否登录:" + StpUtil.isLogin()); -// System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull()); -// StpUtil.setLoginId(id); // 在当前会话登录此账号 -// System.out.println("根据token找登录id:" + StpUtil.getLoginIdByToken(StpUtil.getTokenValue())); - - System.out.println("当前token信息:" + StpUtil.getTokenInfo()); // 获取登录id并转为int - System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull()); - + return AjaxJson.getSuccess(); + } + + // 测试退出登录 , 浏览器访问: http://localhost:8081/test/logout + @RequestMapping("logout") + public AjaxJson logout() { + StpUtil.logout(); +// StpUtil.logoutByLoginId(10001); return AjaxJson.getSuccess(); } @@ -91,17 +97,17 @@ public class TestController { return AjaxJson.getSuccess(); } - // 测试会话session接口, 浏览器访问: http://localhost:8081/test/session @RequestMapping("session") - public AjaxJson session() { + public AjaxJson session() throws JsonProcessingException { System.out.println("======================= 进入方法,测试会话session接口 ========================= "); System.out.println("当前是否登录:" + StpUtil.isLogin()); System.out.println("当前登录账号session的id" + StpUtil.getSession().getId()); System.out.println("当前登录账号session的id" + StpUtil.getSession().getId()); System.out.println("测试取值name:" + StpUtil.getSession().getAttribute("name")); - StpUtil.getSession().setAttribute("name", "张三"); // 写入一个值 + StpUtil.getSession().setAttribute("name", new Date()); // 写入一个值 System.out.println("测试取值name:" + StpUtil.getSession().getAttribute("name")); + System.out.println( new ObjectMapper().writeValueAsString(StpUtil.getSession())); return AjaxJson.getSuccess(); } @@ -155,10 +161,10 @@ public class TestController { return AjaxJson.getSuccess(); } - // 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/atLogin - @SaCheckLogin // 注解式鉴权:当前会话必须登录才能通过 - @RequestMapping("atLogin") - public AjaxJson atLogin() { + // 测试注解式鉴权, 浏览器访问: http://localhost:8081/test/atJurOr + @RequestMapping("atJurOr") + @SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR) // 注解式鉴权:只要具有其中一个权限即可通过校验 + public AjaxJson atJurOr() { return AjaxJson.getSuccessData("用户信息"); } @@ -175,10 +181,8 @@ public class TestController { public AjaxJson kickOut() { // 先登录上 StpUtil.setLoginId(10001); - // 清退下线 -// StpUtil.logoutByLoginId(10001); // 踢下线 - StpUtil.kickoutByLoginId(10001); + StpUtil.logoutByLoginId(10001); // 再尝试获取 StpUtil.getLoginId(); // 返回 @@ -188,15 +192,7 @@ public class TestController { // 测试 浏览器访问: http://localhost:8081/test/test @RequestMapping("test") public AjaxJson test() { - StpUtil.setLoginId(10001); -// StpUtil.getSession(); - StpUtil.logout(); - -// System.out.println(StpUtil.getSession().getId()); -// System.out.println(StpUserUtil.getSession().getId()); -// StpUtil.getSessionByLoginId(10001).setAttribute("name", "123"); -// System.out.println(StpUtil.getSessionByLoginId(10001).getAttribute("name")); - + StpUtil.getTokenSession().logout(); return AjaxJson.getSuccess(); } diff --git a/sa-token-demo-springboot/src/main/resources/application.yml b/sa-token-demo-springboot/src/main/resources/application.yml index 97f4bb9b..118661fb 100644 --- a/sa-token-demo-springboot/src/main/resources/application.yml +++ b/sa-token-demo-springboot/src/main/resources/application.yml @@ -9,21 +9,14 @@ spring: token-name: satoken # token有效期,单位s 默认30天, -1代表永不过期 timeout: 2592000 - # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒, 默认-1 代表不限制 (例如可以设置为1800代表30分钟内无操作就过期) + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 activity-timeout: -1 - # 在多人登录同一账号时,是否共享会话 (为true时共用一个,为false时新登录挤掉旧登录) + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + allow-concurrent-login: true + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) is-share: true - # 是否尝试从请求体里读取token - is-read-body: true - # 是否尝试从header里读取token - is-read-head: true - # 是否尝试从cookie里读取token - is-read-cookie: true # token风格 token-style: uuid - # 是否在初始化配置时打印版本字符画 - is-v: true - tokenSessionCheckLogin: false # redis配置 diff --git a/sa-token-doc/doc/README.md b/sa-token-doc/doc/README.md index a497595e..c30805e6 100644 --- a/sa-token-doc/doc/README.md +++ b/sa-token-doc/doc/README.md @@ -1,11 +1,11 @@

logo

-

sa-token v1.7.0

+

sa-token v1.8.0

一个JavaWeb轻量级权限认证框架,功能全面,上手简单

- + @@ -17,28 +17,29 @@ ## 😘 在线资料 -- ##### [官网首页:http://sa-token.dev33.cn/](http://sa-token.dev33.cn/) -- ##### [在线文档:http://sa-token.dev33.cn/doc/index.html](http://sa-token.dev33.cn/doc/index.html) -- ##### [需求提交:我们深知一个优秀的项目需要海纳百川,点我在线提交需求](http://sa-app.dev33.cn/wall.html?name=sa-token) -- ##### [开源不易,求鼓励,点个star吧](https://github.com/click33/sa-token) + +- [官网首页:http://sa-token.dev33.cn/](http://sa-token.dev33.cn/) + +- [在线文档:http://sa-token.dev33.cn/doc/index.html](http://sa-token.dev33.cn/doc/index.html) + +- [需求提交:我们深知一个优秀的项目需要海纳百川,点我在线提交需求](http://sa-app.dev33.cn/wall.html?name=sa-token) + +- [开源不易,求鼓励,点个star吧](https://github.com/click33/sa-token) ## ⭐ sa-token是什么? -- **sa-token是一个JavaWeb轻量级权限认证框架,其API调用非常简单,有多简单呢?以登录验证为例,你只需要:** +**sa-token是一个JavaWeb轻量级权限认证框架,其API调用非常简单,有多简单呢?以登录验证为例,你只需要:** + ``` java // 在登录时写入当前会话的账号id -StpUtil.setLoginId(10001); +StpUtil.setLoginId(10001); + +// 然后在任意需要校验登录处调用以下API --- 如果当前会话未登录,这句代码会抛出 `NotLoginException`异常 +StpUtil.checkLogin(); ``` -- **然后在任意需要验证登录权限的地方:** -``` java -// 检测是否登录 --- 如果当前会话未登录,下面这句代码会抛出 `NotLoginException`异常 -StpUtil.checkLogin(); -``` - - -- **没有复杂的封装!不要任何的配置!先写入,后鉴权!只需这两行简单的调用,即可轻松完成系统登录鉴权!** +**没有复杂的封装!不要任何的配置!只需这两行简单的调用,即可轻松完成系统登录鉴权!** ## 🔥 框架设计思想 @@ -47,20 +48,38 @@ StpUtil.checkLogin(); - 功能强大:能涵盖的功能全部涵盖,不让你用个框架还要自己给框架打各种补丁 +**如果上面的示例能够证明`sa-token`的简单,那么以下API则可以证明`sa-token`的强大** +``` java +StpUtil.setLoginId(10001); // 标记当前会话登录的账号id +StpUtil.getLoginId(); // 获取当前会话登录的账号id +StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false +StpUtil.logout(); // 当前会话注销登录 +StpUtil.logoutByLoginId(10001); // 让账号为10001的会话注销登录(踢人下线) +StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false +StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false +StpUtil.getSession(); // 获取当前账号id的Session +StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session +StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值 +``` +**sa-token的API众多,请恕此处无法为您逐一展示,更多示例请戳官方在线文档** + + + + ## 💦️️ 涵盖功能 -- ⚡ **登录验证** —— 轻松登录鉴权,并提供五种细分场景值 -- ⚡ **权限验证** —— 拦截违规调用,不同角色不同授权 -- ⚡ **自定义session会话** —— 专业的数据缓存中心 -- ⚡ **踢人下线** —— 将违规用户立刻清退下线 -- ⚡ **模拟他人账号** —— 实时操作任意用户状态数据 -- ⚡ **持久层扩展** —— 可集成redis、MongoDB等专业缓存中间件 -- ⚡ **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权 -- ⚡ **无cookie模式** —— APP、小程序等前后台分离场景 -- ⚡ **注解式鉴权** —— 优雅的将鉴权与业务代码分离 -- ⚡ **花式token生成** —— 内置六种token风格,还可自定义token生成策略 -- ⚡ **自动续签** —— 提供两种token过期策略,灵活搭配使用,还可自动续签 -- ⚡ **组件自动注入** —— 零配置与Spring等框架集成 -- ⚡ **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流 +- **登录验证** —— 轻松登录鉴权,并提供五种细分场景值 +- **权限验证** —— 拦截违规调用,不同角色不同授权 +- **Session会话** —— 专业的数据缓存中心 +- **踢人下线** —— 将违规用户立刻清退下线 +- **模拟他人账号** —— 实时操作任意用户状态数据 +- **持久层扩展** —— 可集成redis、MongoDB等专业缓存中间件 +- **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权 +- **无Cookie模式** —— APP、小程序等前后台分离场景 +- **注解式鉴权** —— 优雅的将鉴权与业务代码分离 +- **花式token生成** —— 内置六种token风格,还可自定义token生成策略 +- **自动续签** —— 提供两种token过期策略,灵活搭配使用,还可自动续签 +- **组件自动注入** —— 零配置与Spring等框架集成 +- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流 ## 🔨 贡献代码 diff --git a/sa-token-doc/doc/_sidebar.md b/sa-token-doc/doc/_sidebar.md index 3819b545..f0db84c1 100644 --- a/sa-token-doc/doc/_sidebar.md +++ b/sa-token-doc/doc/_sidebar.md @@ -8,10 +8,10 @@ - **使用** - [登录验证](/use/login-auth) - [权限验证](/use/jur-auth) - - [session会话](/use/session) + - [Session会话](/use/session) - [踢人下线](/use/kick) - - [持久层扩展(集成redis)](/use/dao-extend) - - [无cookie模式(前后台分离)](/use/not-cookie) + - [持久层扩展(集成Redis)](/use/dao-extend) + - [无Cookie模式(前后台分离)](/use/not-cookie) - [模拟他人](/use/mock-person) - [多账号验证](/use/many-account) - [注解式鉴权](/use/at-check) diff --git a/sa-token-doc/doc/index.html b/sa-token-doc/doc/index.html index d181edd8..511f2746 100644 --- a/sa-token-doc/doc/index.html +++ b/sa-token-doc/doc/index.html @@ -7,7 +7,7 @@ - + @@ -22,6 +22,7 @@