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 84d0556e..7a0ddb7c 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 @@ -318,7 +318,10 @@ public class StpLogic { if(config.getIsConcurrent()) { // 如果配置为共享token, 则尝试从Session签名记录里取出token if(getConfigOfIsShare()) { - tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault()); + // 为确保 jwt-simple 模式的 token Extra 数据生成不受旧token影响,这里必须确保 is-share 配置项在 ExtraData 为空时才可以生效 + if(loginModel.getExtraData() == null || loginModel.getExtraData().size() == 0) { + tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault()); + } } else { // } diff --git a/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/satoken/SaTokenConfigure.java index 5bcbd02a..b4069300 100644 --- a/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/satoken/SaTokenConfigure.java +++ b/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -6,7 +6,7 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import cn.dev33.satoken.interceptor.SaAnnotationInterceptor; -import cn.dev33.satoken.jwt.StpLogicJwtForStyle; +import cn.dev33.satoken.jwt.StpLogicJwtForSimple; import cn.dev33.satoken.stp.StpLogic; @@ -32,7 +32,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { */ @Bean public StpLogic getStpLogicJwt() { - return new StpLogicJwtForStyle(); + return new StpLogicJwtForSimple(); } } diff --git a/sa-token-demo/sa-token-demo-sso3-client-nosdk/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso3-client-nosdk/src/main/resources/application.yml index 33d5a653..bb9f457b 100644 --- a/sa-token-demo/sa-token-demo-sso3-client-nosdk/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso3-client-nosdk/src/main/resources/application.yml @@ -2,6 +2,10 @@ server: port: 9001 +forest: + # 打开/关闭Forest请求日志(默认为 true) + log-request: true + spring: # Redis连接 redis: diff --git a/sa-token-doc/doc/plugin/jwt-extend.md b/sa-token-doc/doc/plugin/jwt-extend.md index b5a90f9f..3c11c169 100644 --- a/sa-token-doc/doc/plugin/jwt-extend.md +++ b/sa-token-doc/doc/plugin/jwt-extend.md @@ -32,38 +32,38 @@ sa-token: - -Style 模式:Token 风格替换 + +Simple 模式:Token 风格替换 ``` java @Configuration public class SaTokenConfigure { - // Sa-Token 整合 jwt (Style模式) + // Sa-Token 整合 jwt (Simple 简单模式) @Bean public StpLogic getStpLogicJwt() { - return new StpLogicJwtForStyle(); + return new StpLogicJwtForSimple(); } } ``` - -Mix 模式:混入部分逻辑 + +Mixin 模式:混入部分逻辑 ``` java @Configuration public class SaTokenConfigure { - // Sa-Token 整合 jwt (Mix模式) + // Sa-Token 整合 jwt (Mixin 混入模式) @Bean public StpLogic getStpLogicJwt() { - return new StpLogicJwtForMix(); + return new StpLogicJwtForMixin(); } } ``` - + Stateless 模式:服务器完全无状态 ``` java @Configuration public class SaTokenConfigure { - // Sa-Token 整合 jwt (Stateless模式) + // Sa-Token 整合 jwt (Stateless 无状态模式) @Bean public StpLogic getStpLogicJwt() { return new StpLogicJwtForStateless(); @@ -134,7 +134,7 @@ String name = StpUtil.getExtra("name"); 注入不同模式会让框架具有不同的行为策略,以下是三种模式的差异点(为方便叙述,以下比较以同时引入 jwt 与 Redis 作为前提): -| 功能点 | Style 模式 | Mix 模式 | Stateless 模式 | +| 功能点 | Simple 简单模式 | Mixin 混入模式 | Stateless 无状态模式 | | :-------- | :-------- | :-------- | :-------- | | Token风格 | jwt风格 | jwt风格 | jwt风格 | | 登录数据存储 | Redis中 | Token中 | Token中 | diff --git a/sa-token-doc/doc/start/download.md b/sa-token-doc/doc/start/download.md index d5e5ea1b..a2446420 100644 --- a/sa-token-doc/doc/start/download.md +++ b/sa-token-doc/doc/start/download.md @@ -102,6 +102,7 @@ implementation 'cn.dev33:sa-token-core:${sa.top.version}' ├── sa-token-temp-jwt // [插件] Sa-Token 整合 jwt 临时令牌鉴权 ├── sa-token-quick-login // [插件] Sa-Token 快速注入登录页插件 ├── sa-token-alone-redis // [插件] Sa-Token 独立Redis插件,实现[权限缓存与业务缓存分离] + ├── sa-token-sso // [插件] Sa-Token 整合 SSO 单点登录 ├── sa-token-oauth2 // [插件] Sa-Token 实现 OAuth2.0 模块 ├── sa-token-dialect-thymeleaf // [插件] Sa-Token 标签方言(Thymeleaf版) ├── sa-token-jwt // [插件] Sa-Token 整合 jwt 登录认证 diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java index b7472101..66f02dcb 100644 --- a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java @@ -1,16 +1,18 @@ package cn.dev33.satoken.jwt; import java.util.Map; +import java.util.Objects; import cn.dev33.satoken.dao.SaTokenDao; -import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.jwt.exception.SaJwtException; +import cn.dev33.satoken.jwt.exception.SaJwtExceptionCode; import cn.dev33.satoken.util.SaFoxUtil; import cn.hutool.json.JSONObject; import cn.hutool.jwt.JWT; import cn.hutool.jwt.JWTException; /** - * jwt操作工具类封装 + * jwt 操作工具类封装 * @author kong * */ @@ -36,27 +38,38 @@ public class SaJwtUtil { */ public static final String EFF = "eff"; + /** + * key:乱数 ( 混入随机字符串,防止每次生成的 token 都是一样的 ) + */ + public static final String RN_STR = "rnStr"; + /** * 当有效期被设为此值时,代表永不过期 */ public static final long NEVER_EXPIRE = SaTokenDao.NEVER_EXPIRE; + + /** + * 表示一个值不存在 + */ + public static final long NOT_VALUE_EXPIRE = SaTokenDao.NOT_VALUE_EXPIRE; // ------ 创建 /** * 创建 jwt (简单方式) + * @param loginType 登录类型 * @param loginId 账号id * @param extraData 扩展数据 * @param keyt 秘钥 * @return jwt-token */ - public static String createToken(Object loginId, Map extraData, String keyt) { + public static String createToken(String loginType, Object loginId, Map extraData, String keyt) { // 构建 String token = JWT.create() + .setPayload(LOGIN_TYPE, loginType) .setPayload(LOGIN_ID, loginId) - // 混入随机字符串,防止每次生成的 token 都是一样的 - .setPayload("rn", SaFoxUtil.getRandomString(32)) + .setPayload(RN_STR, SaFoxUtil.getRandomString(32)) .addPayloads(extraData) .setKey(keyt.getBytes()) .sign(); @@ -90,6 +103,7 @@ public class SaJwtUtil { .setPayload(LOGIN_ID, loginId) .setPayload(DEVICE, device) .setPayload(EFF, effTime) + .setPayload(RN_STR, SaFoxUtil.getRandomString(32)) .addPayloads(extraData); // 返回 @@ -99,16 +113,18 @@ public class SaJwtUtil { // ------ 解析 /** - * jwt 解析(校验签名和有效期) + * jwt 解析 * @param token Jwt-Token值 + * @param loginType 登录类型 * @param keyt 秘钥 + * @param isCheckTimeout 是否校验 timeout 字段 * @return 解析后的jwt 对象 */ - public static JWT parseToken(String token, String keyt) { + public static JWT parseToken(String token, String loginType, String keyt, boolean isCheckTimeout) { // 如果token为null if(token == null) { - throw NotLoginException.newInstance(null, NotLoginException.NOT_TOKEN); + throw new SaJwtException("jwt 字符串不可为空"); } // 解析 @@ -116,23 +132,29 @@ public class SaJwtUtil { try { jwt = JWT.of(token); } catch (JWTException e) { - // 解析失败 - throw NotLoginException.newInstance(null, NotLoginException.INVALID_TOKEN, token); + throw new SaJwtException("jwt 解析失败:" + token, e).setCode(SaJwtExceptionCode.CODE_40101); } JSONObject payloads = jwt.getPayloads(); // 校验 Token 签名 boolean verify = jwt.setKey(keyt.getBytes()).verify(); if(verify == false) { - throw NotLoginException.newInstance(payloads.getStr(LOGIN_TYPE), NotLoginException.INVALID_TOKEN, token); + throw new SaJwtException("jwt 签名无效:" + token).setCode(SaJwtExceptionCode.CODE_40102); }; + + // 校验 loginType + if(Objects.equals(loginType, payloads.getStr(LOGIN_TYPE)) == false) { + throw new SaJwtException("jwt loginType 无效:" + token).setCode(SaJwtExceptionCode.CODE_40103); + } // 校验 Token 有效期 - Long effTime = payloads.getLong(EFF, 0L); - if(effTime != NEVER_EXPIRE) { - if(effTime == null || effTime < System.currentTimeMillis()) { - throw NotLoginException.newInstance(payloads.getStr(LOGIN_TYPE), NotLoginException.TOKEN_TIMEOUT, token); - } + if(isCheckTimeout) { + Long effTime = payloads.getLong(EFF, 0L); + if(effTime != NEVER_EXPIRE) { + if(effTime == null || effTime < System.currentTimeMillis()) { + throw new SaJwtException("jwt 已过期:" + token).setCode(SaJwtExceptionCode.CODE_40104); + } + } } // 返回 @@ -140,58 +162,49 @@ public class SaJwtUtil { } /** - * 获取 jwt 数据载荷 (校验签名和有效期) + * 获取 jwt 数据载荷 (校验 sign、loginType、timeout) * @param token token值 + * @param loginType 登录类型 * @param keyt 秘钥 * @return 载荷 */ - public static JSONObject getPayloads(String token, String keyt) { - return parseToken(token, keyt).getPayloads(); + public static JSONObject getPayloads(String token, String loginType, String keyt) { + return parseToken(token, loginType, keyt, true).getPayloads(); } /** - * 获取 jwt 数据载荷 (只校验签名,不校验有效期) + * 获取 jwt 数据载荷 (校验 sign、loginType,不校验 timeout) * @param token token值 + * @param loginType 登录类型 * @param keyt 秘钥 * @return 载荷 */ - public static JSONObject getPayloadsNotCheck(String token, String keyt) { - try { - JWT jwt = JWT.of(token); - JSONObject payloads = jwt.getPayloads(); - - // 校验 Token 签名 - boolean verify = jwt.setKey(keyt.getBytes()).verify(); - if(verify == false) { - throw NotLoginException.newInstance(payloads.getStr(LOGIN_TYPE), NotLoginException.INVALID_TOKEN, token); - }; - - return payloads; - } catch (JWTException e) { - return new JSONObject(); - } + public static JSONObject getPayloadsNotCheck(String token, String loginType, String keyt) { + return parseToken(token, loginType, keyt, false).getPayloads(); } /** * 获取 jwt 代表的账号id * @param token Token值 + * @param loginType 登录类型 * @param keyt 秘钥 * @return 值 */ - public static Object getLoginId(String token, String keyt) { - return getPayloads(token, keyt).get(LOGIN_ID); + public static Object getLoginId(String token, String loginType, String keyt) { + return getPayloads(token, loginType, keyt).get(LOGIN_ID); } /** * 获取 jwt 代表的账号id (未登录时返回null) * @param token Token值 + * @param loginType 登录类型 * @param keyt 秘钥 * @return 值 */ - public static Object getLoginIdOrNull(String token, String keyt) { + public static Object getLoginIdOrNull(String token, String loginType, String keyt) { try { - return getPayloads(token, keyt).get(LOGIN_ID); - } catch (NotLoginException e) { + return getPayloads(token, loginType, keyt).get(LOGIN_ID); + } catch (SaJwtException e) { return null; } } @@ -199,14 +212,15 @@ public class SaJwtUtil { /** * 获取 jwt 剩余有效期 * @param token JwtToken值 + * @param loginType 登录类型 * @param keyt 秘钥 * @return 值 */ - public static long getTimeout(String token, String keyt) { + public static long getTimeout(String token, String loginType, String keyt) { // 如果token为null if(token == null) { - return SaTokenDao.NOT_VALUE_EXPIRE; + return NOT_VALUE_EXPIRE; } // 取出数据 @@ -215,15 +229,20 @@ public class SaJwtUtil { jwt = JWT.of(token); } catch (JWTException e) { // 解析失败 - return SaTokenDao.NOT_VALUE_EXPIRE; + return NOT_VALUE_EXPIRE; } JSONObject payloads = jwt.getPayloads(); // 如果签名无效 boolean verify = jwt.setKey(keyt.getBytes()).verify(); if(verify == false) { - return SaTokenDao.NOT_VALUE_EXPIRE; + return NOT_VALUE_EXPIRE; }; + + // 如果 loginType 无效 + if(Objects.equals(loginType, payloads.getStr(LOGIN_TYPE)) == false) { + return NOT_VALUE_EXPIRE; + } // 如果被设置为:永不过期 Long effTime = payloads.get(EFF, Long.class); @@ -232,38 +251,11 @@ public class SaJwtUtil { } // 如果已经超时 if(effTime == null || effTime < System.currentTimeMillis()) { - return SaTokenDao.NOT_VALUE_EXPIRE; + return NOT_VALUE_EXPIRE; } // 计算timeout (转化为以秒为单位的有效时间) return (effTime - System.currentTimeMillis()) / 1000; } - // ------ 废弃API - - /** - * 创建 jwt (简单方式) - * @param loginId 账号id - * @param keyt 秘钥 - * @return jwt-token - */ - @Deprecated - public static String createToken(Object loginId, String keyt) { - return createToken(loginId, null, keyt); - } - - /** - * 创建 jwt (全参数方式) - * @param loginType 账号类型 - * @param loginId 账号id - * @param device 设备类型 - * @param timeout token有效期 (单位 秒) - * @param keyt 秘钥 - * @return jwt-token - */ - @Deprecated - public static String createToken(String loginType, Object loginId, String device, long timeout, String keyt) { - return createToken(loginType, loginId, device, timeout, null, keyt); - } - } diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMix.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMix.java index 80d795f6..a5676c6f 100644 --- a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMix.java +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMix.java @@ -1,221 +1,12 @@ package cn.dev33.satoken.jwt; -import java.util.List; -import java.util.Map; - -import cn.dev33.satoken.context.SaHolder; -import cn.dev33.satoken.dao.SaTokenDao; -import cn.dev33.satoken.exception.ApiDisabledException; -import cn.dev33.satoken.exception.NotLoginException; -import cn.dev33.satoken.exception.SaTokenException; -import cn.dev33.satoken.stp.SaTokenInfo; -import cn.dev33.satoken.stp.StpLogic; -import cn.dev33.satoken.stp.StpUtil; - /** - * Sa-Token 整合 jwt -- Mix 混入 + * 已更名为 StpLogicJwtForMixin,请更换 + * * @author kong - * + * @date: 2022-5-1 */ -public class StpLogicJwtForMix extends StpLogic { +@Deprecated +public class StpLogicJwtForMix extends StpLogicJwtForMixin { - /** - * Sa-Token 整合 jwt -- Mix 混入 - */ - public StpLogicJwtForMix() { - super(StpUtil.TYPE); - } - - /** - * Sa-Token 整合 jwt -- Mix 混入 - * @param loginType 账号体系标识 - */ - public StpLogicJwtForMix(String loginType) { - super(loginType); - } - - /** - * 获取jwt秘钥 - * @return / - */ - public String jwtSecretKey() { - String keyt = getConfig().getJwtSecretKey(); - SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); - return keyt; - } - - // - // ------ 重写方法 - // - - // ------------------- 获取token 相关 ------------------- - - /** - * 创建一个TokenValue - */ - @Override - public String createTokenValue(Object loginId, String device, long timeout, Map extraData) { - return SaJwtUtil.createToken(loginType, loginId, device, timeout, extraData, jwtSecretKey()); - } - - /** - * 获取当前会话的Token信息 - * @return token信息 - */ - @Override - public SaTokenInfo getTokenInfo() { - SaTokenInfo info = new SaTokenInfo(); - info.tokenName = getTokenName(); - info.tokenValue = getTokenValue(); - info.isLogin = isLogin(); - info.loginId = getLoginIdDefaultNull(); - info.loginType = getLoginType(); - info.tokenTimeout = getTokenTimeout(); - info.sessionTimeout = SaTokenDao.NOT_VALUE_EXPIRE; - info.tokenSessionTimeout = SaTokenDao.NOT_VALUE_EXPIRE; - info.tokenActivityTimeout = SaTokenDao.NOT_VALUE_EXPIRE; - info.loginDevice = getLoginDevice(); - return info; - } - - // ------------------- 登录相关操作 ------------------- - - /** - * 获取指定Token对应的账号id (不做任何特殊处理) - */ - @Override - public String getLoginIdNotHandle(String tokenValue) { - // 先验证 loginType,如果不符,则视为无效token,返回null - String loginType = SaJwtUtil.getPayloadsNotCheck(tokenValue, jwtSecretKey()).getStr(SaJwtUtil.LOGIN_TYPE); - if(getLoginType().equals(loginType) == false) { - return null; - } - // 获取 loginId - try { - Object loginId = SaJwtUtil.getLoginId(tokenValue, jwtSecretKey()); - return String.valueOf(loginId); - } catch (NotLoginException e) { - return null; - } - } - - /** - * 会话注销 - */ - @Override - public void logout() { - // ... - - // 从当前 [storage存储器] 里删除 - SaHolder.getStorage().delete(splicingKeyJustCreatedSave()); - - // 如果打开了Cookie模式,则把cookie清除掉 - if(getConfig().getIsReadCookie()){ - SaHolder.getResponse().deleteCookie(getTokenName()); - } - } - - /** - * [禁用] 会话注销,根据账号id 和 设备类型 - */ - @Override - public void logout(Object loginId, String device) { - throw new ApiDisabledException(); - } - - /** - * [禁用] 会话注销,根据指定 Token - */ - @Override - public void logoutByTokenValue(String tokenValue) { - throw new ApiDisabledException(); - } - - /** - * [禁用] 踢人下线,根据账号id 和 设备类型 - */ - @Override - public void kickout(Object loginId, String device) { - throw new ApiDisabledException(); - } - - /** - * [禁用] 踢人下线,根据指定 Token - */ - @Override - public void kickoutByTokenValue(String tokenValue) { - throw new ApiDisabledException(); - } - - /** - * [禁用] 顶人下线,根据账号id 和 设备类型 - */ - @Override - public void replaced(Object loginId, String device) { - throw new ApiDisabledException(); - } - - /** - * 获取Token携带的扩展信息 - */ - @Override - public Object getExtra(String key) { - return SaJwtUtil.getPayloads(getTokenValue(), jwtSecretKey()).get(key); - } - - /** - * 删除 Token-Id 映射 - */ - @Override - public void deleteTokenToIdMapping(String tokenValue) { - // not action - } - /** - * 更改 Token 指向的 账号Id 值 - */ - @Override - public void updateTokenToIdMapping(String tokenValue, Object loginId) { - // not action - } - /** - * 存储 Token-Id 映射 - */ - @Override - public void saveTokenToIdMapping(String tokenValue, Object loginId, long timeout) { - // not action - } - - // ------------------- 过期时间相关 ------------------- - - /** - * 获取当前登录者的 token 剩余有效时间 (单位: 秒) - */ - @Override - public long getTokenTimeout() { - return SaJwtUtil.getTimeout(getTokenValue(), jwtSecretKey()); - } - - - // ------------------- 会话管理 ------------------- - - /** - * [禁用] 根据条件查询Token - */ - @Override - public List searchTokenValue(String keyword, int start, int size) { - throw new ApiDisabledException(); - } - - - // ------------------- Bean对象代理 ------------------- - - /** - * 返回全局配置对象的isShare属性 - * @return / - */ - @Override - public boolean getConfigOfIsShare() { - return false; - } - } diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMixin.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMixin.java new file mode 100644 index 00000000..b4afc679 --- /dev/null +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMixin.java @@ -0,0 +1,215 @@ +package cn.dev33.satoken.jwt; + +import java.util.List; +import java.util.Map; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.exception.ApiDisabledException; +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.jwt.exception.SaJwtException; +import cn.dev33.satoken.stp.SaTokenInfo; +import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.stp.StpUtil; + +/** + * Sa-Token 整合 jwt -- Mixin 混入模式 + * @author kong + * + */ +public class StpLogicJwtForMixin extends StpLogic { + + /** + * Sa-Token 整合 jwt -- Mixin 混入 + */ + public StpLogicJwtForMixin() { + super(StpUtil.TYPE); + } + + /** + * Sa-Token 整合 jwt -- Mixin 混入 + * @param loginType 账号体系标识 + */ + public StpLogicJwtForMixin(String loginType) { + super(loginType); + } + + /** + * 获取jwt秘钥 + * @return / + */ + public String jwtSecretKey() { + String keyt = getConfig().getJwtSecretKey(); + SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); + return keyt; + } + + // + // ------ 重写方法 + // + + // ------------------- 获取token 相关 ------------------- + + /** + * 创建一个TokenValue + */ + @Override + public String createTokenValue(Object loginId, String device, long timeout, Map extraData) { + return SaJwtUtil.createToken(loginType, loginId, device, timeout, extraData, jwtSecretKey()); + } + + /** + * 获取当前会话的Token信息 + * @return token信息 + */ + @Override + public SaTokenInfo getTokenInfo() { + SaTokenInfo info = new SaTokenInfo(); + info.tokenName = getTokenName(); + info.tokenValue = getTokenValue(); + info.isLogin = isLogin(); + info.loginId = getLoginIdDefaultNull(); + info.loginType = getLoginType(); + info.tokenTimeout = getTokenTimeout(); + info.sessionTimeout = SaTokenDao.NOT_VALUE_EXPIRE; + info.tokenSessionTimeout = SaTokenDao.NOT_VALUE_EXPIRE; + info.tokenActivityTimeout = SaTokenDao.NOT_VALUE_EXPIRE; + info.loginDevice = getLoginDevice(); + return info; + } + + // ------------------- 登录相关操作 ------------------- + + /** + * 获取指定Token对应的账号id (不做任何特殊处理) + */ + @Override + public String getLoginIdNotHandle(String tokenValue) { + try { + Object loginId = SaJwtUtil.getLoginId(tokenValue, loginType, jwtSecretKey()); + return String.valueOf(loginId); + } catch (SaJwtException e) { + return null; + } + } + + /** + * 会话注销 + */ + @Override + public void logout() { + // ... + + // 从当前 [storage存储器] 里删除 + SaHolder.getStorage().delete(splicingKeyJustCreatedSave()); + + // 如果打开了Cookie模式,则把cookie清除掉 + if(getConfig().getIsReadCookie()){ + SaHolder.getResponse().deleteCookie(getTokenName()); + } + } + + /** + * [禁用] 会话注销,根据账号id 和 设备类型 + */ + @Override + public void logout(Object loginId, String device) { + throw new ApiDisabledException(); + } + + /** + * [禁用] 会话注销,根据指定 Token + */ + @Override + public void logoutByTokenValue(String tokenValue) { + throw new ApiDisabledException(); + } + + /** + * [禁用] 踢人下线,根据账号id 和 设备类型 + */ + @Override + public void kickout(Object loginId, String device) { + throw new ApiDisabledException(); + } + + /** + * [禁用] 踢人下线,根据指定 Token + */ + @Override + public void kickoutByTokenValue(String tokenValue) { + throw new ApiDisabledException(); + } + + /** + * [禁用] 顶人下线,根据账号id 和 设备类型 + */ + @Override + public void replaced(Object loginId, String device) { + throw new ApiDisabledException(); + } + + /** + * 获取Token携带的扩展信息 + */ + @Override + public Object getExtra(String key) { + return SaJwtUtil.getPayloads(getTokenValue(), loginType, jwtSecretKey()).get(key); + } + + /** + * 删除 Token-Id 映射 + */ + @Override + public void deleteTokenToIdMapping(String tokenValue) { + // not action + } + /** + * 更改 Token 指向的 账号Id 值 + */ + @Override + public void updateTokenToIdMapping(String tokenValue, Object loginId) { + // not action + } + /** + * 存储 Token-Id 映射 + */ + @Override + public void saveTokenToIdMapping(String tokenValue, Object loginId, long timeout) { + // not action + } + + // ------------------- 过期时间相关 ------------------- + + /** + * 获取当前登录者的 token 剩余有效时间 (单位: 秒) + */ + @Override + public long getTokenTimeout() { + return SaJwtUtil.getTimeout(getTokenValue(), loginType, jwtSecretKey()); + } + + + // ------------------- 会话管理 ------------------- + + /** + * [禁用] 根据条件查询Token + */ + @Override + public List searchTokenValue(String keyword, int start, int size) { + throw new ApiDisabledException(); + } + + + // ------------------- Bean对象代理 ------------------- + + /** + * 返回全局配置对象的isShare属性 + * @return / + */ + @Override + public boolean getConfigOfIsShare() { + return false; + } + +} diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForSimple.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForSimple.java new file mode 100644 index 00000000..ccb3f9b5 --- /dev/null +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForSimple.java @@ -0,0 +1,59 @@ +package cn.dev33.satoken.jwt; + +import java.util.Map; + +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.stp.StpUtil; + +/** + * Sa-Token 整合 jwt -- Simple 简单模式 + * @author kong + * + */ +public class StpLogicJwtForSimple extends StpLogic { + + /** + * Sa-Token 整合 jwt -- Simple模式 + */ + public StpLogicJwtForSimple() { + super(StpUtil.TYPE); + } + + /** + * Sa-Token 整合 jwt -- Simple模式 + * @param loginType 账号体系标识 + */ + public StpLogicJwtForSimple(String loginType) { + super(loginType); + } + + /** + * 获取jwt秘钥 + * @return / + */ + public String jwtSecretKey() { + String keyt = getConfig().getJwtSecretKey(); + SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); + return keyt; + } + + // ------ 重写方法 + + /** + * 创建一个TokenValue + */ + @Override + public String createTokenValue(Object loginId, String device, long timeout, Map extraData) { + return SaJwtUtil.createToken(loginType, loginId, extraData, jwtSecretKey()); + } + + /** + * 获取Token携带的扩展信息 + */ + @Override + public Object getExtra(String key) { + return SaJwtUtil.getPayloadsNotCheck(getTokenValue(), loginType, jwtSecretKey()).get(key); + } + +} diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStateless.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStateless.java index 658e8633..43bee718 100644 --- a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStateless.java +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStateless.java @@ -1,34 +1,35 @@ package cn.dev33.satoken.jwt; +import java.util.Map; + import cn.dev33.satoken.SaManager; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.exception.ApiDisabledException; -import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.jwt.exception.SaJwtException; import cn.dev33.satoken.stp.SaLoginModel; import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpUtil; - -import java.util.Map; +import cn.dev33.satoken.util.SaFoxUtil; /** - * Sa-Token 整合 jwt -- stateless 无状态 + * Sa-Token 整合 jwt -- Stateless 无状态模式 * @author kong * */ public class StpLogicJwtForStateless extends StpLogic { /** - * Sa-Token 整合 jwt -- stateless 无状态 + * Sa-Token 整合 jwt -- Stateless 无状态 */ public StpLogicJwtForStateless() { super(StpUtil.TYPE); } /** - * Sa-Token 整合 jwt -- stateless 无状态 + * Sa-Token 整合 jwt -- Stateless 无状态 * @param loginType 账号体系标识 */ public StpLogicJwtForStateless(String loginType) { @@ -108,16 +109,10 @@ public class StpLogicJwtForStateless extends StpLogic { */ @Override public String getLoginIdNotHandle(String tokenValue) { - // 先验证 loginType,如果不符,则视为无效token,返回null - String loginType = SaJwtUtil.getPayloadsNotCheck(tokenValue, jwtSecretKey()).getStr(SaJwtUtil.LOGIN_TYPE); - if(getLoginType().equals(loginType) == false) { - return null; - } - // 获取 loginId try { - Object loginId = SaJwtUtil.getLoginId(tokenValue, jwtSecretKey()); + Object loginId = SaJwtUtil.getLoginId(tokenValue, loginType, jwtSecretKey()); return String.valueOf(loginId); - } catch (NotLoginException e) { + } catch (SaJwtException e) { return null; } } @@ -127,7 +122,11 @@ public class StpLogicJwtForStateless extends StpLogic { */ @Override public void logout() { - // ... + // 如果连token都没有,那么无需执行任何操作 + String tokenValue = getTokenValue(); + if(SaFoxUtil.isEmpty(tokenValue)) { + return; + } // 从当前 [storage存储器] 里删除 SaHolder.getStorage().delete(splicingKeyJustCreatedSave()); @@ -143,7 +142,7 @@ public class StpLogicJwtForStateless extends StpLogic { */ @Override public Object getExtra(String key) { - return SaJwtUtil.getPayloads(getTokenValue(), jwtSecretKey()).get(key); + return SaJwtUtil.getPayloads(getTokenValue(), loginType, jwtSecretKey()).get(key); } @@ -154,7 +153,7 @@ public class StpLogicJwtForStateless extends StpLogic { */ @Override public long getTokenTimeout() { - return SaJwtUtil.getTimeout(getTokenValue(), jwtSecretKey()); + return SaJwtUtil.getTimeout(getTokenValue(), loginType, jwtSecretKey()); } @@ -176,7 +175,7 @@ public class StpLogicJwtForStateless extends StpLogic { return null; } // 获取 - return SaJwtUtil.getPayloadsNotCheck(tokenValue, jwtSecretKey()).getStr(SaJwtUtil.DEVICE); + return SaJwtUtil.getPayloadsNotCheck(tokenValue, loginType, jwtSecretKey()).getStr(SaJwtUtil.DEVICE); } diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStyle.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStyle.java index dfed3d54..a9880184 100644 --- a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStyle.java +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStyle.java @@ -1,59 +1,12 @@ package cn.dev33.satoken.jwt; -import java.util.Map; - -import cn.dev33.satoken.exception.SaTokenException; -import cn.dev33.satoken.stp.StpLogic; -import cn.dev33.satoken.stp.StpUtil; - /** - * Sa-Token 整合 jwt -- Style模式 + * 已更名为 StpLogicJwtForSimple,请更换 + * * @author kong - * + * @date: 2022-5-1 */ -public class StpLogicJwtForStyle extends StpLogic { - - /** - * Sa-Token 整合 jwt -- Style模式 - */ - public StpLogicJwtForStyle() { - super(StpUtil.TYPE); - } - - /** - * Sa-Token 整合 jwt -- Style模式 - * @param loginType 账号体系标识 - */ - public StpLogicJwtForStyle(String loginType) { - super(loginType); - } - - /** - * 获取jwt秘钥 - * @return / - */ - public String jwtSecretKey() { - String keyt = getConfig().getJwtSecretKey(); - SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); - return keyt; - } - - // ------ 重写方法 - - /** - * 创建一个TokenValue - */ - @Override - public String createTokenValue(Object loginId, String device, long timeout, Map extraData) { - return SaJwtUtil.createToken(loginId, extraData, jwtSecretKey()); - } - - /** - * 获取Token携带的扩展信息 - */ - @Override - public Object getExtra(String key) { - return SaJwtUtil.getPayloadsNotCheck(getTokenValue(), jwtSecretKey()).get(key); - } +@Deprecated +public class StpLogicJwtForStyle extends StpLogicJwtForSimple { } diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/exception/SaJwtException.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/exception/SaJwtException.java new file mode 100644 index 00000000..2b8a6cd3 --- /dev/null +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/exception/SaJwtException.java @@ -0,0 +1,56 @@ +package cn.dev33.satoken.jwt.exception; + +import cn.dev33.satoken.exception.SaTokenException; + + +/** + * 一个异常:代表 jwt 解析错误 + * + * @author kong + */ +public class SaJwtException extends SaTokenException { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129555290130114L; + + /** + * jwt 解析错误 + * @param message 异常描述 + */ + public SaJwtException(String message) { + super(message); + } + + /** + * jwt 解析错误 + * @param message 异常描述 + * @param cause 异常对象 + */ + public SaJwtException(String message, Throwable cause) { + super(message, cause); + } + + /** + * 写入异常细分状态码 + * @param code 异常细分状态码 + * @return 对象自身 + */ + public SaJwtException setCode(int code) { + super.setCode(code); + return this; + } + + /** + * 如果flag==true,则抛出message异常 + * @param flag 标记 + * @param message 异常信息 + */ + public static void throwBy(boolean flag, String message) { + if(flag) { + throw new SaJwtException(message); + } + } + +} diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/exception/SaJwtExceptionCode.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/exception/SaJwtExceptionCode.java new file mode 100644 index 00000000..94116eb6 --- /dev/null +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/exception/SaJwtExceptionCode.java @@ -0,0 +1,23 @@ +package cn.dev33.satoken.jwt.exception; + +/** + * 定义所有 JWT 异常细分状态码 + * + * @author kong + * @date: 2022-5-1 + */ +public class SaJwtExceptionCode { + + /** 对 jwt 字符串解析失败 */ + public static final int CODE_40101 = 40101; + + /** 此 jwt 的签名无效 */ + public static final int CODE_40102 = 40102; + + /** 此 jwt 的 loginType 字段不符合预期 */ + public static final int CODE_40103 = 40103; + + /** 此 jwt 已超时 */ + public static final int CODE_40104 = 40104; + +} diff --git a/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForMixTest.java b/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForMixinTest.java similarity index 93% rename from sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForMixTest.java rename to sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForMixinTest.java index a42e58b7..0c9c3c31 100644 --- a/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForMixTest.java +++ b/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForMixinTest.java @@ -14,21 +14,22 @@ import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.exception.ApiDisabledException; import cn.dev33.satoken.exception.DisableLoginException; import cn.dev33.satoken.jwt.SaJwtUtil; -import cn.dev33.satoken.jwt.StpLogicJwtForMix; +import cn.dev33.satoken.jwt.StpLogicJwtForMixin; import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.SaLoginConfig; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaTokenConsts; import cn.hutool.json.JSONObject; import cn.hutool.jwt.JWT; /** - * Sa-Token 整合 jwt:mix 模式 测试 + * Sa-Token 整合 jwt:mixin 模式 测试 * * @author kong * */ @SpringBootTest(classes = StartUpApplication.class) -public class JwtForMixTest { +public class JwtForMixinTest { // 持久化Bean @Autowired(required = false) @@ -38,7 +39,7 @@ public class JwtForMixTest { @BeforeAll public static void beforeClass() { System.out.println("\n\n------------------------ JwtForMixTest star ..."); - StpUtil.setStpLogic(new StpLogicJwtForMix()); + StpUtil.setStpLogic(new StpLogicJwtForMixin()); } // 结束 @@ -254,5 +255,17 @@ public class JwtForMixTest { Assertions.assertTrue(list.size() >= 5); }); } + + // 测试:getExtra + @Test + public void getExtra() { + // 登录 + StpUtil.login(10001, SaLoginConfig.setExtra("name", "zhangsan")); + + // 可以取到 + Assertions.assertEquals(StpUtil.getExtra("name"), "zhangsan"); + // 取不到 + Assertions.assertEquals(StpUtil.getExtra("name2"), null); + } } diff --git a/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForStyleTest.java b/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForSimpleTest.java similarity index 78% rename from sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForStyleTest.java rename to sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForSimpleTest.java index c1a5d35c..88c31e2a 100644 --- a/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForStyleTest.java +++ b/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForSimpleTest.java @@ -8,22 +8,23 @@ import org.springframework.boot.test.context.SpringBootTest; import cn.dev33.satoken.SaManager; import cn.dev33.satoken.dao.SaTokenDao; -import cn.dev33.satoken.jwt.StpLogicJwtForStyle; +import cn.dev33.satoken.jwt.StpLogicJwtForSimple; import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.stp.SaLoginConfig; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaTokenConsts; import cn.hutool.json.JSONObject; import cn.hutool.jwt.JWT; /** - * Sa-Token 整合 jwt:Style 模式 测试 + * Sa-Token 整合 jwt:Simple 模式 测试 * * @author kong * */ //@RunWith(SpringRunner.class) @SpringBootTest(classes = StartUpApplication.class) -public class JwtForStyleTest { +public class JwtForSimpleTest { // 持久化Bean static SaTokenDao dao; @@ -33,7 +34,7 @@ public class JwtForStyleTest { public static void beforeClass() { System.out.println("\n\n------------------------ JwtForStyleTest star ..."); dao = SaManager.getSaTokenDao(); - StpUtil.setStpLogic(new StpLogicJwtForStyle()); + StpUtil.setStpLogic(new StpLogicJwtForSimple()); } // 结束 @@ -69,5 +70,17 @@ public class JwtForStyleTest { Assertions.assertEquals(session.getId(), "satoken:login:session:" + 10001); Assertions.assertTrue(session.getTokenSignList().size() >= 1); } + + // 测试:getExtra + @Test + public void getExtra() { + // 登录 + StpUtil.login(10001, SaLoginConfig.setExtra("name", "zhangsan")); + + // 可以取到 + Assertions.assertEquals(StpUtil.getExtra("name"), "zhangsan"); + // 取不到 + Assertions.assertEquals(StpUtil.getExtra("name2"), null); + } } diff --git a/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForStatelessTest.java b/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForStatelessTest.java index 0e299178..43a123c7 100644 --- a/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForStatelessTest.java +++ b/sa-token-test/sa-token-jwt-test/src/test/java/com/pj/test/JwtForStatelessTest.java @@ -12,6 +12,7 @@ import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.exception.ApiDisabledException; import cn.dev33.satoken.jwt.SaJwtUtil; import cn.dev33.satoken.jwt.StpLogicJwtForStateless; +import cn.dev33.satoken.stp.SaLoginConfig; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaTokenConsts; import cn.hutool.json.JSONObject; @@ -164,4 +165,16 @@ public class JwtForStatelessTest { }); } + // 测试:getExtra + @Test + public void getExtra() { + // 登录 + StpUtil.login(10001, SaLoginConfig.setExtra("name", "zhangsan")); + + // 可以取到 + Assertions.assertEquals(StpUtil.getExtra("name"), "zhangsan"); + // 取不到 + Assertions.assertEquals(StpUtil.getExtra("name2"), null); + } + }