From 6d26761fd55c656299ec2ca99f709305513273ed Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Mon, 18 Oct 2021 22:05:26 +0800 Subject: [PATCH] =?UTF-8?q?v1.27.1=20=E6=96=B0=E5=A2=9Ejwt=E9=9B=86?= =?UTF-8?q?=E6=88=90=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev33/satoken/config/SaTokenConfig.java | 6 +- .../satoken/exception/NotLoginException.java | 26 +- .../java/cn/dev33/satoken/stp/StpLogic.java | 72 +++--- .../java/cn/dev33/satoken/stp/StpUtil.java | 2 +- sa-token-demo/sa-token-demo-jwt/pom.xml | 48 +--- .../com/pj/SaTokenJwtDemoApplication.java | 2 +- .../java/com/pj/satoken/SaTokenConfigure.java | 38 +++ .../com/pj/satoken/jwt/SaTokenJwtUtil.java | 235 ------------------ .../java/com/pj/test/GlobalException.java | 53 ---- .../java/com/pj/test/TestJwtController.java | 8 +- .../src/main/resources/application.yml | 3 + sa-token-doc/doc/_sidebar.md | 2 +- sa-token-doc/doc/index.html | 2 +- sa-token-doc/doc/micro/gateway-auth.md | 2 +- sa-token-doc/doc/more/common-action.md | 2 +- sa-token-doc/doc/plugin/temp-token.md | 6 +- sa-token-doc/doc/sso/sso-type2.md | 2 +- sa-token-doc/doc/sso/sso-type3.md | 8 + sa-token-doc/doc/up/global-filter.md | 2 +- sa-token-doc/doc/up/many-account.md | 8 +- sa-token-doc/doc/use/jur-auth.md | 3 +- sa-token-doc/doc/use/route-check.md | 2 +- sa-token-doc/index.html | 2 +- sa-token-plugin/pom.xml | 1 + sa-token-plugin/sa-token-jwt/.gitignore | 12 + sa-token-plugin/sa-token-jwt/pom.xml | 33 +++ .../java/cn/dev33/satoken/jwt/SaJwtUtil.java | 235 ++++++++++++++++++ .../satoken/jwt/StpLogicJwtForStateless.java | 188 ++++++++++++++ .../satoken/jwt/StpLogicJwtForTokenStyle.java | 49 ++++ .../test/java/com/pj/test/ManyLoginTest.java | 16 +- 30 files changed, 679 insertions(+), 389 deletions(-) create mode 100644 sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/satoken/SaTokenConfigure.java delete mode 100644 sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/satoken/jwt/SaTokenJwtUtil.java create mode 100644 sa-token-plugin/sa-token-jwt/.gitignore create mode 100644 sa-token-plugin/sa-token-jwt/pom.xml create mode 100644 sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java create mode 100644 sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStateless.java create mode 100644 sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForTokenStyle.java 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 1983868e..0b5bc781 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 @@ -63,7 +63,7 @@ public class SaTokenConfig implements Serializable { private Boolean isLog = false; /** - * jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效) + * jwt秘钥 (只有集成 jwt 模块时此参数才会生效) */ private String jwtSecretKey; @@ -337,14 +337,14 @@ public class SaTokenConfig implements Serializable { } /** - * @return jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效) + * @return jwt秘钥 (只有集成 jwt 模块时此参数才会生效) */ public String getJwtSecretKey() { return jwtSecretKey; } /** - * @param jwtSecretKey jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效) + * @param jwtSecretKey jwt秘钥 (只有集成 jwt 模块时此参数才会生效) * @return 对象自身 */ public SaTokenConfig setJwtSecretKey(String jwtSecretKey) { diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/exception/NotLoginException.java b/sa-token-core/src/main/java/cn/dev33/satoken/exception/NotLoginException.java index 37c1e8f6..80a07abd 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/exception/NotLoginException.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/exception/NotLoginException.java @@ -3,6 +3,8 @@ package cn.dev33.satoken.exception; import java.util.Arrays; import java.util.List; +import cn.dev33.satoken.util.SaFoxUtil; + /** * 一个异常:代表会话未能通过登录认证 * @author kong @@ -24,23 +26,23 @@ public class NotLoginException extends SaTokenException { /** 表示未提供token */ public static final String NOT_TOKEN = "-1"; - public static final String NOT_TOKEN_MESSAGE = "未提供token"; + public static final String NOT_TOKEN_MESSAGE = "未提供Token"; /** 表示token无效 */ public static final String INVALID_TOKEN = "-2"; - public static final String INVALID_TOKEN_MESSAGE = "token无效"; + public static final String INVALID_TOKEN_MESSAGE = "Token无效"; /** 表示token已过期 */ public static final String TOKEN_TIMEOUT = "-3"; - public static final String TOKEN_TIMEOUT_MESSAGE = "token已过期"; + public static final String TOKEN_TIMEOUT_MESSAGE = "Token已过期"; /** 表示token已被顶下线 */ public static final String BE_REPLACED = "-4"; - public static final String BE_REPLACED_MESSAGE = "token已被顶下线"; + public static final String BE_REPLACED_MESSAGE = "Token已被顶下线"; /** 表示token已被踢下线 */ public static final String KICK_OUT = "-5"; - public static final String KICK_OUT_MESSAGE = "token已被踢下线"; + public static final String KICK_OUT_MESSAGE = "Token已被踢下线"; /** 默认的提示语 */ public static final String DEFAULT_MESSAGE = "当前会话未登录"; @@ -99,6 +101,17 @@ public class NotLoginException extends SaTokenException { * @return 构建完毕的异常对象 */ public static NotLoginException newInstance(String loginType, String type) { + return newInstance(loginType, type, null); + } + + /** + * 静态方法构建一个NotLoginException + * @param loginType 账号类型 + * @param type 账号类型 + * @param token 引起异常的Token值 + * @return 构建完毕的异常对象 + */ + public static NotLoginException newInstance(String loginType, String type, String token) { String message = null; if(NOT_TOKEN.equals(type)) { message = NOT_TOKEN_MESSAGE; @@ -118,6 +131,9 @@ public class NotLoginException extends SaTokenException { else { message = DEFAULT_MESSAGE; } + if(SaFoxUtil.isEmpty(token) == false) { + message = message + ":" + token; + } return new NotLoginException(message, loginType, type); } 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 e8d7452d..76b195be 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 @@ -504,19 +504,19 @@ public class StpLogic { // 查找此token对应loginId, 如果找不到则抛出:无效token String loginId = getLoginIdNotHandle(tokenValue); if(loginId == null) { - throw NotLoginException.newInstance(loginType, NotLoginException.INVALID_TOKEN); + throw NotLoginException.newInstance(loginType, NotLoginException.INVALID_TOKEN, tokenValue); } // 如果是已经过期,则抛出已经过期 if(loginId.equals(NotLoginException.TOKEN_TIMEOUT)) { - throw NotLoginException.newInstance(loginType, NotLoginException.TOKEN_TIMEOUT); + throw NotLoginException.newInstance(loginType, NotLoginException.TOKEN_TIMEOUT, tokenValue); } // 如果是已经被顶替下去了, 则抛出:已被顶下线 if(loginId.equals(NotLoginException.BE_REPLACED)) { - throw NotLoginException.newInstance(loginType, NotLoginException.BE_REPLACED); + throw NotLoginException.newInstance(loginType, NotLoginException.BE_REPLACED, tokenValue); } // 如果是已经被踢下线了, 则抛出:已被踢下线 if(loginId.equals(NotLoginException.KICK_OUT)) { - throw NotLoginException.newInstance(loginType, NotLoginException.KICK_OUT); + throw NotLoginException.newInstance(loginType, NotLoginException.KICK_OUT, tokenValue); } // 检查是否已经 [临时过期] checkActivityTimeout(tokenValue); @@ -623,7 +623,7 @@ public class StpLogic { * @return 账号id */ public String getLoginIdNotHandle(String tokenValue) { - return SaManager.getSaTokenDao().get(splicingKeyTokenValue(tokenValue)); + return getSaTokenDao().get(splicingKeyTokenValue(tokenValue)); } // ---- 其它操作 @@ -640,7 +640,7 @@ public class StpLogic { * @param tokenValue token值 */ public void deleteTokenToIdMapping(String tokenValue) { - SaManager.getSaTokenDao().delete(splicingKeyTokenValue(tokenValue)); + getSaTokenDao().delete(splicingKeyTokenValue(tokenValue)); } /** * 更改 Token 指向的 账号Id 值 @@ -649,7 +649,7 @@ public class StpLogic { */ public void updateTokenToIdMapping(String tokenValue, Object loginId) { SaTokenException.throwBy(SaFoxUtil.isEmpty(loginId), "LoginId 不能为空"); - SaManager.getSaTokenDao().update(splicingKeyTokenValue(tokenValue), loginId.toString()); + getSaTokenDao().update(splicingKeyTokenValue(tokenValue), loginId.toString()); } /** * 存储 Token-Id 映射 @@ -658,7 +658,7 @@ public class StpLogic { * @param timeout 会话有效期 (单位: 秒) */ public void saveTokenToIdMapping(String tokenValue, Object loginId, long timeout) { - SaManager.getSaTokenDao().set(splicingKeyTokenValue(tokenValue), String.valueOf(loginId), timeout); + getSaTokenDao().set(splicingKeyTokenValue(tokenValue), String.valueOf(loginId), timeout); } @@ -672,10 +672,10 @@ public class StpLogic { * @return Session对象 */ public SaSession getSessionBySessionId(String sessionId, boolean isCreate) { - SaSession session = SaManager.getSaTokenDao().getSession(sessionId); + SaSession session = getSaTokenDao().getSession(sessionId); if(session == null && isCreate) { session = SaStrategy.me.createSession.apply(sessionId); - SaManager.getSaTokenDao().setSession(session, getConfig().getTimeout()); + getSaTokenDao().setSession(session, getConfig().getTimeout()); } return session; } @@ -786,10 +786,10 @@ public class StpLogic { * @param tokenValue token值 */ public void deleteTokenSession(String tokenValue) { - SaManager.getSaTokenDao().delete(splicingKeyTokenSession(tokenValue)); + getSaTokenDao().delete(splicingKeyTokenSession(tokenValue)); } - // ------------------- [临时过期] 验证相关 ------------------- + // ------------------- [临时有效期] 验证相关 ------------------- /** * 写入指定token的 [最后操作时间] 为当前时间戳 @@ -801,7 +801,7 @@ public class StpLogic { return; } // 将[最后操作时间]标记为当前时间戳 - SaManager.getSaTokenDao().set(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout()); + getSaTokenDao().set(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout()); } /** @@ -814,7 +814,7 @@ public class StpLogic { return; } // 删除[最后操作时间] - SaManager.getSaTokenDao().delete(splicingKeyLastActivityTime(tokenValue)); + getSaTokenDao().delete(splicingKeyLastActivityTime(tokenValue)); // 清除标记 SaHolder.getStorage().delete(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY); } @@ -842,7 +842,7 @@ public class StpLogic { } // -2 代表已过期,抛出异常 if(timeout == SaTokenDao.NOT_VALUE_EXPIRE) { - throw NotLoginException.newInstance(loginType, NotLoginException.TOKEN_TIMEOUT); + throw NotLoginException.newInstance(loginType, NotLoginException.TOKEN_TIMEOUT, tokenValue); } // --- 至此,验证已通过 @@ -866,7 +866,7 @@ public class StpLogic { if(tokenValue == null || getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) { return; } - SaManager.getSaTokenDao().update(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis())); + getSaTokenDao().update(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis())); } /** @@ -886,7 +886,7 @@ public class StpLogic { * @return token剩余有效时间 */ public long getTokenTimeout() { - return SaManager.getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValue())); + return getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValue())); } /** @@ -895,7 +895,7 @@ public class StpLogic { * @return token剩余有效时间 */ public long getTokenTimeoutByLoginId(Object loginId) { - return SaManager.getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValueByLoginId(loginId))); + return getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValueByLoginId(loginId))); } /** @@ -912,7 +912,7 @@ public class StpLogic { * @return token剩余有效时间 */ public long getSessionTimeoutByLoginId(Object loginId) { - return SaManager.getSaTokenDao().getSessionTimeout(splicingKeySession(loginId)); + return getSaTokenDao().getSessionTimeout(splicingKeySession(loginId)); } /** @@ -929,7 +929,7 @@ public class StpLogic { * @return token剩余有效时间 */ public long getTokenSessionTimeoutByTokenValue(String tokenValue) { - return SaManager.getSaTokenDao().getSessionTimeout(splicingKeyTokenSession(tokenValue)); + return getSaTokenDao().getSessionTimeout(splicingKeyTokenSession(tokenValue)); } /** @@ -957,7 +957,7 @@ public class StpLogic { // ------ 开始查询 // 获取相关数据 String keyLastActivityTime = splicingKeyLastActivityTime(tokenValue); - String lastActivityTimeString = SaManager.getSaTokenDao().get(keyLastActivityTime); + String lastActivityTimeString = getSaTokenDao().get(keyLastActivityTime); // 查不到,返回-2 if(lastActivityTimeString == null) { return SaTokenDao.NOT_VALUE_EXPIRE; @@ -1299,7 +1299,7 @@ public class StpLogic { * @return token集合 */ public List searchTokenValue(String keyword, int start, int size) { - return SaManager.getSaTokenDao().searchData(splicingKeyTokenValue(""), keyword, start, size); + return getSaTokenDao().searchData(splicingKeyTokenValue(""), keyword, start, size); } /** @@ -1310,7 +1310,7 @@ public class StpLogic { * @return sessionId集合 */ public List searchSessionId(String keyword, int start, int size) { - return SaManager.getSaTokenDao().searchData(splicingKeySession(""), keyword, start, size); + return getSaTokenDao().searchData(splicingKeySession(""), keyword, start, size); } /** @@ -1321,7 +1321,7 @@ public class StpLogic { * @return sessionId集合 */ public List searchTokenSessionId(String keyword, int start, int size) { - return SaManager.getSaTokenDao().searchData(splicingKeyTokenSession(""), keyword, start, size); + return getSaTokenDao().searchData(splicingKeyTokenSession(""), keyword, start, size); } @@ -1394,7 +1394,7 @@ public class StpLogic { */ public void disable(Object loginId, long disableTime) { // 标注为已被封禁 - SaManager.getSaTokenDao().set(splicingKeyDisable(loginId), DisableLoginException.BE_VALUE, disableTime); + getSaTokenDao().set(splicingKeyDisable(loginId), DisableLoginException.BE_VALUE, disableTime); // $$ 通知监听器 SaManager.getSaTokenListener().doDisable(loginType, loginId, disableTime); @@ -1406,7 +1406,7 @@ public class StpLogic { * @return see note */ public boolean isDisable(Object loginId) { - return SaManager.getSaTokenDao().get(splicingKeyDisable(loginId)) != null; + return getSaTokenDao().get(splicingKeyDisable(loginId)) != null; } /** @@ -1415,7 +1415,7 @@ public class StpLogic { * @return see note */ public long getDisableTime(Object loginId) { - return SaManager.getSaTokenDao().getTimeout(splicingKeyDisable(loginId)); + return getSaTokenDao().getTimeout(splicingKeyDisable(loginId)); } /** @@ -1423,7 +1423,7 @@ public class StpLogic { * @param loginId 账号id */ public void untieDisable(Object loginId) { - SaManager.getSaTokenDao().delete(splicingKeyDisable(loginId)); + getSaTokenDao().delete(splicingKeyDisable(loginId)); // $$ 通知监听器 SaManager.getSaTokenListener().doUntieDisable(loginType, loginId); @@ -1607,13 +1607,21 @@ public class StpLogic { // ------------------- Bean对象代理 ------------------- /** - * 返回配置对象 - * @return 配置对象 + * 返回全局配置对象 + * @return / */ public SaTokenConfig getConfig() { // 为什么再次代理一层? 为某些极端业务场景下[需要不同StpLogic不同配置]提供便利 return SaManager.getConfig(); } + + /** + * 返回持久化对象 + * @return / + */ + public SaTokenDao getSaTokenDao() { + return SaManager.getSaTokenDao(); + } /** * 判断:集合中是否包含指定元素(模糊匹配) @@ -1628,7 +1636,7 @@ public class StpLogic { // ------------------- 历史API,兼容旧版本 ------------------- /** - *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变

+ *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout(id) ,使用方式保持不变

* * 会话注销,根据账号id (踢人下线) *

当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 @@ -1639,7 +1647,7 @@ public class StpLogic { } /** - *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变

+ *

本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout(id) ,使用方式保持不变

* * 会话注销,根据账号id and 设备标识 (踢人下线) *

当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2

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 5658f14a..f6b858e1 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 @@ -340,7 +340,7 @@ public class StpUtil { } - // =================== [临时过期] 验证相关 =================== + // =================== [临时有效期] 验证相关 =================== /** * 检查当前token 是否已经[临时过期],如果已经过期则抛出异常 diff --git a/sa-token-demo/sa-token-demo-jwt/pom.xml b/sa-token-demo/sa-token-demo-jwt/pom.xml index 98903cf0..b4b69519 100644 --- a/sa-token-demo/sa-token-demo-jwt/pom.xml +++ b/sa-token-demo/sa-token-demo-jwt/pom.xml @@ -26,52 +26,32 @@ org.springframework.boot spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-aop - - + cn.dev33 sa-token-spring-boot-starter ${sa-token-version} - - - io.jsonwebtoken - jjwt - 0.9.1 - - - - + cn.dev33 - sa-token-dao-redis + sa-token-jwt ${sa-token-version} - --> - - - + cn.dev33 sa-token-dao-redis-jackson ${sa-token-version} - --> - - - + - - - org.springframework.boot @@ -79,12 +59,6 @@ true - - javax.xml.bind - jaxb-api - - - diff --git a/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/SaTokenJwtDemoApplication.java b/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/SaTokenJwtDemoApplication.java index f06b978e..fa383986 100644 --- a/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/SaTokenJwtDemoApplication.java +++ b/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/SaTokenJwtDemoApplication.java @@ -10,7 +10,7 @@ public class SaTokenJwtDemoApplication { public static void main(String[] args) { SpringApplication.run(SaTokenJwtDemoApplication.class, args); - System.out.println("\n启动成功:sa-token配置如下:" + SaManager.getConfig()); + System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig()); } } \ No newline at end of file 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 new file mode 100644 index 00000000..5aae843f --- /dev/null +++ b/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -0,0 +1,38 @@ +package com.pj.satoken; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import cn.dev33.satoken.interceptor.SaAnnotationInterceptor; +import cn.dev33.satoken.jwt.StpLogicJwtForStateless; +import cn.dev33.satoken.stp.StpLogic; + + +/** + * [Sa-Token 权限认证] 配置类 + * @author kong + * + */ +@Configuration +public class SaTokenConfigure implements WebMvcConfigurer { + + /** + * 注册Sa-Token 的拦截器,打开注解式鉴权功能 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册注解拦截器 + registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**"); + } + + /** + * Sa-Token 整合 jwt + */ + @Bean + public StpLogic getStpLogicJwt() { + return new StpLogicJwtForStateless(); + } + +} diff --git a/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/satoken/jwt/SaTokenJwtUtil.java b/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/satoken/jwt/SaTokenJwtUtil.java deleted file mode 100644 index 802cabcf..00000000 --- a/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/satoken/jwt/SaTokenJwtUtil.java +++ /dev/null @@ -1,235 +0,0 @@ -package com.pj.satoken.jwt; - -import java.util.Date; - -import org.springframework.stereotype.Component; - -import cn.dev33.satoken.SaManager; -import cn.dev33.satoken.config.SaTokenConfig; -import cn.dev33.satoken.context.model.SaStorage; -import cn.dev33.satoken.dao.SaTokenDao; -import cn.dev33.satoken.exception.NotLoginException; -import cn.dev33.satoken.exception.SaTokenException; -import cn.dev33.satoken.session.SaSession; -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 cn.dev33.satoken.util.SaTokenConsts; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.JwtBuilder; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.SignatureAlgorithm; - -@Component -public class SaTokenJwtUtil { - - /** - * 秘钥 (随便手打几个字母就好了) - */ - public static final String BASE64_SECURITY = "79e7c69681b8270162386e6daa53d1dd"; - - /** - * token有效期 (单位: 秒) - */ - public static final long TIMEOUT = 60 * 60 * 2; - - - public static final String LOGIN_ID_KEY = "loginId"; - - - /** - * 根据userId生成token - * @param loginId 账号id - * @param base64Security 秘钥 - * @return jwt-token - */ - public static String createToken(Object loginId) { - // 判断,不可使用默认秘钥 -// if(BASE64_SECURITY.equals("79e7c69681b8270162386e6daa53d1dd")) { -// throw new SaTokenException("请更换秘钥"); -// } - // 在这里你可以使用官方提供的claim方法构建载荷,也可以使用setPayload自定义载荷,但是两者不可一起使用 - JwtBuilder builder = Jwts.builder() - .setHeaderParam("type", "JWT") - .claim(LOGIN_ID_KEY, loginId) - .setIssuedAt(new Date()) // 签发日期 - .setExpiration(new Date(System.currentTimeMillis() + 1000 * TIMEOUT)) // 有效截止日期 - .signWith(SignatureAlgorithm.HS256, BASE64_SECURITY.getBytes()); // 加密算法 - //生成JWT - return builder.compact(); - } - - /** - * 从一个jwt里面解析出Claims - * @param tokenValue token值 - * @param base64Security 秘钥 - * @return Claims对象 - */ - public static Claims getClaims(String tokenValue) { -// System.out.println(tokenValue); - Claims claims = Jwts.parser() - .setSigningKey(BASE64_SECURITY.getBytes()) - .parseClaimsJws(tokenValue).getBody(); - return claims; - } - - /** - * 从一个jwt里面解析loginId - * @param tokenValue token值 - * @param base64Security 秘钥 - * @return loginId - */ - public static String getLoginId(String tokenValue) { - try { - Object loginId = getClaims(tokenValue).get(LOGIN_ID_KEY); - if(loginId == null) { - return null; - } - return String.valueOf(loginId); - } catch (ExpiredJwtException e) { -// throw NotLoginException.newInstance(StpUtil.TYPE, NotLoginException.TOKEN_TIMEOUT); - return NotLoginException.TOKEN_TIMEOUT; - } catch (MalformedJwtException e) { - throw NotLoginException.newInstance(StpUtil.stpLogic.loginType, NotLoginException.INVALID_TOKEN); - } catch (Exception e) { - throw new SaTokenException(e); - } - } - - - - static { - - // 判断秘钥 - if(BASE64_SECURITY.equals("79e7c69681b8270162386e6daa53d1dd")) { - String warn = "-------------------------------------\n"; - warn += "请更换JWT秘钥,不要使用示例默认秘钥\n"; - warn += "-------------------------------------"; - System.err.println(warn); - } - - // 提前调用一下方法,促使其属性初始化 - StpUtil.getLoginType(); - - // 修改默认实现 - StpUtil.stpLogic = new StpLogic("login") { - - // 重写 (随机生成一个tokenValue) - @Override - public String createTokenValue(Object loginId) { - return SaTokenJwtUtil.createToken(loginId); - } - - // 重写 (在当前会话上登录id ) - @Override - public void login(Object loginId, SaLoginModel loginModel) { - // ------ 1、获取相应对象 - SaStorage storage = SaManager.getSaTokenContext().getStorage(); - SaTokenConfig config = getConfig(); - // ------ 2、生成一个token - String tokenValue = createTokenValue(loginId); - storage.set(splicingKeyJustCreatedSave(), tokenValue); // 将token保存到本次request里 - if(config.getIsReadCookie() == true){ // cookie注入 - SaManager.getSaTokenContext().getResponse().addCookie(getTokenName(), tokenValue, "/", config.getCookie().getDomain(), (int)config.getTimeout()); - } - } - - // 重写 (获取指定token对应的登录id) - @Override - public String getLoginIdNotHandle(String tokenValue) { - try { - return SaTokenJwtUtil.getLoginId(tokenValue); - } catch (Exception e) { - return null; - } - } - - // 重写 (当前会话注销登录) - @Override - public void logout() { - // 如果连token都没有,那么无需执行任何操作 - String tokenValue = getTokenValue(); - if(tokenValue == null) { - return; - } - // 如果打开了cookie模式,把cookie清除掉 - if(getConfig().getIsReadCookie() == true){ - SaManager.getSaTokenContext().getResponse().deleteCookie(getTokenName()); - } - } - - // 重写 (获取指定key的session) - @Override - public SaSession getSessionBySessionId(String sessionId, boolean isCreate) { - throw new SaTokenException("jwt has not session"); - } - - // 重写 (获取当前登录者的token剩余有效时间 (单位: 秒)) - @Override - public long getTokenTimeout() { - // 如果没有token - String tokenValue = getTokenValue(); - if(tokenValue == null) { - return SaTokenDao.NOT_VALUE_EXPIRE; - } - // 开始取值 - Claims claims = null; - try { - claims = SaTokenJwtUtil.getClaims(tokenValue); - } catch (Exception e) { - return SaTokenDao.NOT_VALUE_EXPIRE; - } - if(claims == null) { - return SaTokenDao.NOT_VALUE_EXPIRE; - } - Date expiration = claims.getExpiration(); - if(expiration == null) { - return SaTokenDao.NOT_VALUE_EXPIRE; - } - return (expiration.getTime() - System.currentTimeMillis()) / 1000; - } - - // 重写 (返回当前token的登录设备) - @Override - public String getLoginDevice() { - return SaTokenConsts.DEFAULT_LOGIN_DEVICE; - } - - // 重写 (获取当前会话的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 = getSessionTimeout(); -// info.tokenSessionTimeout = getTokenSessionTimeout(); -// info.tokenActivityTimeout = getTokenActivityTimeout(); - info.loginDevice = getLoginDevice(); - return info; - } - - - }; - - } - - - - - - - - - - - - - -} diff --git a/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/test/GlobalException.java b/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/test/GlobalException.java index f128bcac..42083dc6 100644 --- a/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/test/GlobalException.java +++ b/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/test/GlobalException.java @@ -1,12 +1,9 @@ package com.pj.test; -import java.io.IOException; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RestControllerAdvice; import com.pj.util.AjaxJson; @@ -21,15 +18,6 @@ import cn.dev33.satoken.exception.NotRoleException; @RestControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin") public class GlobalException { - // 在每个控制器之前触发的操作 - @ModelAttribute - public void get(HttpServletRequest request) throws IOException { - - } - - - - // 全局异常拦截(拦截项目中的所有异常) @ExceptionHandler public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) @@ -55,46 +43,5 @@ public class GlobalException { // 返回给前端 return aj; - - // 输出到客户端 -// response.setContentType("application/json; charset=utf-8"); // http说明,我要返回JSON对象 -// response.getWriter().print(new ObjectMapper().writeValueAsString(aj)); } - - - - // 全局异常拦截(拦截项目中的NotLoginException异常) -// @ExceptionHandler(NotLoginException.class) -// public AjaxJson handlerNotLoginException(NotLoginException nle, HttpServletRequest request, HttpServletResponse response) -// throws Exception { -// -// // 打印堆栈,以供调试 -// nle.printStackTrace(); -// -// // 判断场景值,定制化异常信息 -// String message = ""; -// if(nle.getType().equals(NotLoginException.NOT_TOKEN)) { -// message = "未提供token"; -// } -// else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) { -// message = "token无效"; -// } -// else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) { -// message = "token已过期"; -// } -// else if(nle.getType().equals(NotLoginException.BE_REPLACED)) { -// message = "token已被顶下线"; -// } -// else if(nle.getType().equals(NotLoginException.KICK_OUT)) { -// message = "token已被踢下线"; -// } -// else { -// message = "当前会话未登录"; -// } -// -// // 返回给前端 -// return AjaxJson.getError(message); -// } - - } diff --git a/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/test/TestJwtController.java b/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/test/TestJwtController.java index 6111c13b..db7416a5 100644 --- a/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/test/TestJwtController.java +++ b/sa-token-demo/sa-token-demo-jwt/src/main/java/com/pj/test/TestJwtController.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.pj.util.AjaxJson; +import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpUtil; @@ -22,8 +23,6 @@ import cn.dev33.satoken.stp.StpUtil; @RequestMapping("/test/") public class TestJwtController { - - // 测试登录接口, 浏览器访问: http://localhost:8081/test/login @RequestMapping("login") public AjaxJson login(@RequestParam(defaultValue="10001") String id) { @@ -51,7 +50,7 @@ public class TestJwtController { System.out.println(tokenInfo); return AjaxJson.getSuccessData(tokenInfo); } - + // 测试会话session接口, 浏览器访问: http://localhost:8081/test/session @RequestMapping("session") @@ -70,11 +69,10 @@ public class TestJwtController { // 测试 浏览器访问: http://localhost:8081/test/test @RequestMapping("test") + @SaCheckLogin public AjaxJson test() { System.out.println(); System.out.println("--------------进入请求--------------"); - StpUtil.login(10001); - System.out.println(StpUtil.getTokenInfo().getTokenValue()); return AjaxJson.getSuccess(); } diff --git a/sa-token-demo/sa-token-demo-jwt/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-jwt/src/main/resources/application.yml index 14a193d4..0f148e62 100644 --- a/sa-token-demo/sa-token-demo-jwt/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-jwt/src/main/resources/application.yml @@ -16,6 +16,9 @@ sa-token: is-share: true # token风格 token-style: uuid + # jwt秘钥 + jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk + spring: # redis配置 redis: diff --git a/sa-token-doc/doc/_sidebar.md b/sa-token-doc/doc/_sidebar.md index 04d39842..a9fe9b34 100644 --- a/sa-token-doc/doc/_sidebar.md +++ b/sa-token-doc/doc/_sidebar.md @@ -59,7 +59,7 @@ - **插件** - [AOP注解鉴权](/plugin/aop-at) - - [临时Token验证](/plugin/temp-token) + - [临时Token认证](/plugin/temp-token) - [Quick-Login快速登录插件](/plugin/quick-login) - [Alone独立Redis插件](/plugin/alone-redis) - [持久层扩展](/plugin/dao-extend) diff --git a/sa-token-doc/doc/index.html b/sa-token-doc/doc/index.html index cc99240a..7595b0cb 100644 --- a/sa-token-doc/doc/index.html +++ b/sa-token-doc/doc/index.html @@ -4,7 +4,7 @@ Sa-Token - + diff --git a/sa-token-doc/doc/micro/gateway-auth.md b/sa-token-doc/doc/micro/gateway-auth.md index 9f367eb6..20689f74 100644 --- a/sa-token-doc/doc/micro/gateway-auth.md +++ b/sa-token-doc/doc/micro/gateway-auth.md @@ -83,7 +83,7 @@ public class SaTokenConfigure { .addExclude("/favicon.ico") // 鉴权方法:每次访问进入 .setAuth(obj -> { - // 登录验证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 + // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin()); // 权限认证 -- 不同模块, 校验不同权限 diff --git a/sa-token-doc/doc/more/common-action.md b/sa-token-doc/doc/more/common-action.md index a6796af7..c1397499 100644 --- a/sa-token-doc/doc/more/common-action.md +++ b/sa-token-doc/doc/more/common-action.md @@ -12,7 +12,7 @@ SaManager.getStpInterface(); // 获取权限认证对象 SaManager.getSaTokenAction(); // 获取框架行为对象 SaManager.getSaTokenContext(); // 获取上下文处理对象 SaManager.getSaTokenListener(); // 获取侦听器对象 -SaManager.getSaTemp(); // 获取临时令牌验证模块对象 +SaManager.getSaTemp(); // 获取临时令牌认证模块对象 SaManager.getStpLogic("type"); // 获取指定账号类型的StpLogic对象 ``` diff --git a/sa-token-doc/doc/plugin/temp-token.md b/sa-token-doc/doc/plugin/temp-token.md index 88a483e6..e1e1a0f4 100644 --- a/sa-token-doc/doc/plugin/temp-token.md +++ b/sa-token-doc/doc/plugin/temp-token.md @@ -1,4 +1,4 @@ -# 临时Token令牌验证 +# 临时Token令牌认证 --- @@ -37,7 +37,7 @@ http://xxx.com/apply?token=oEwQBnglXDoGraSJdGaLooPZnGrk ### 相关API -**[sa-token-temp临时验证模块]** 已内嵌到核心包,无需引入其它依赖即可使用 +**[sa-token-temp临时认证模块]** 已内嵌到核心包,无需引入其它依赖即可使用 ``` java // 根据 value 创建一个 token @@ -55,7 +55,7 @@ SaTempUtil.deleteToken(token); ### 集成jwt -提到 [临时Token验证],你是不是想到一个专门干这件事的框架?对,就是JWT! +提到 [临时Token认证],你是不是想到一个专门干这件事的框架?对,就是JWT! **[sa-token-temp]** 模块允许以JWT作为逻辑内核完成工作,你只需要引入以下依赖,所有上层API保持不变 diff --git a/sa-token-doc/doc/sso/sso-type2.md b/sa-token-doc/doc/sso/sso-type2.md index d930a0fd..c0f20a72 100644 --- a/sa-token-doc/doc/sso/sso-type2.md +++ b/sa-token-doc/doc/sso/sso-type2.md @@ -198,7 +198,7 @@ public class SaSsoClientApplication { 至此,测试完毕! -可以看出,除了在`Client1`端我们需要手动登录一次之外,在`Client2端`和`Client3端`都是可以无需验证,直接登录成功的。 +可以看出,除了在`Client1`端我们需要手动登录一次之外,在`Client2端`和`Client3端`都是可以无需再次认证,直接登录成功的。 我们可以通过 F12控制台 Netword跟踪整个过程 diff --git a/sa-token-doc/doc/sso/sso-type3.md b/sa-token-doc/doc/sso/sso-type3.md index b9fc6a27..8f9c58cb 100644 --- a/sa-token-doc/doc/sso/sso-type3.md +++ b/sa-token-doc/doc/sso/sso-type3.md @@ -107,6 +107,14 @@ public Object myinfo() { 访问测试:[http://sa-sso-client1.com:9001/sso/myinfo](http://sa-sso-client1.com:9001/sso/myinfo) +#### 3.3、疑问 + +群里有小伙伴提问:`SaSsoUtil.getUserinfo` 提供的参数太少,只有一个 loginId,无法满足业务需求怎么办? + +答:SaSsoUtil.getUserinfo只是为了避免你在项目中硬编码认证中心 url 而提供的简易封装,如果这个API无法满足你的业务需求, +你完全可以在 Server 端自定义一些接口然后从 Client 端使用 http 工具调用即可。 + + ### 4、无刷单点注销 diff --git a/sa-token-doc/doc/up/global-filter.md b/sa-token-doc/doc/up/global-filter.md index 3e4d4d1e..d4d62dc1 100644 --- a/sa-token-doc/doc/up/global-filter.md +++ b/sa-token-doc/doc/up/global-filter.md @@ -42,7 +42,7 @@ public class SaTokenConfigure { .setAuth(obj -> { System.out.println("---------- 进入Sa-Token全局认证 -----------"); - // 登录验证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 + // 登录认证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录 SaRouter.match("/**", "/user/doLogin", () -> StpUtil.checkLogin()); // 更多拦截处理方式,请参考“路由拦截式鉴权”章节 diff --git a/sa-token-doc/doc/up/many-account.md b/sa-token-doc/doc/up/many-account.md index 2632dbc7..d7738ca5 100644 --- a/sa-token-doc/doc/up/many-account.md +++ b/sa-token-doc/doc/up/many-account.md @@ -5,7 +5,7 @@ 有的时候,我们会在一个项目中设计两套账号体系,比如一个电商系统的 `user表` 和 `admin表`, 在这种场景下,如果两套账号我们都使用 `StpUtil` 类的API进行登录鉴权,那么势必会发生逻辑冲突 -在Sa-Token中,这个问题的模型叫做:多账号体系验证 +在Sa-Token中,这个问题的模型叫做:多账号体系认证 要解决这个问题,我们必须有一个合理的机制将这两套账号的授权给区分开,让它们互不干扰才行 @@ -22,7 +22,7 @@ ### 2、解决方案 -前面几篇介绍的api调用,都是经过 StpUtil 类的各种静态方法进行授权验证, +前面几篇介绍的api调用,都是经过 StpUtil 类的各种静态方法进行授权认证, 而如果我们深入它的源码,[点此阅览](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java)
就会发现,此类并没有任何代码逻辑,唯一做的事就是对成员变量`stpLogic`的各个API包装一下进行转发 @@ -33,8 +33,8 @@ ### 3、操作示例 -比如说,对于原生`StpUtil`类,我们只做`admin账号`权限验证,而对于`user账号`,我们则: -1. 新建一个新的权限验证类,比如: `StpUserUtil.java` +比如说,对于原生`StpUtil`类,我们只做`admin账号`权限认证,而对于`user账号`,我们则: +1. 新建一个新的权限认证类,比如: `StpUserUtil.java` 2. 将`StpUtil.java`类的全部代码复制粘贴到 `StpUserUtil.java`里 3. 更改一下其 `LoginType`, 比如: diff --git a/sa-token-doc/doc/use/jur-auth.md b/sa-token-doc/doc/use/jur-auth.md index 3315229b..51a1334d 100644 --- a/sa-token-doc/doc/use/jur-auth.md +++ b/sa-token-doc/doc/use/jur-auth.md @@ -112,7 +112,8 @@ StpUtil.checkRoleOr("super-admin", "shop-admin"); ### 拦截全局异常 有同学要问,鉴权失败,抛出异常,然后呢?要把异常显示给用户看吗?**当然不可以!**
-你可以创建一个全局异常拦截器,统一返回给前端的格式,参考:[码云:GlobalException.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java) +你可以创建一个全局异常拦截器,统一返回给前端的格式,参考: +[码云:GlobalException.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/current/GlobalException.java) ### 权限通配符 diff --git a/sa-token-doc/doc/use/route-check.md b/sa-token-doc/doc/use/route-check.md index 0a9add93..262b80c7 100644 --- a/sa-token-doc/doc/use/route-check.md +++ b/sa-token-doc/doc/use/route-check.md @@ -2,7 +2,7 @@ --- 假设我们有如下需求: -> 项目中所有接口均需要登录验证,只有'登录接口'本身对外开放 +> 项目中所有接口均需要登录认证,只有'登录接口'本身对外开放 我们怎么实现呢?给每个接口加上鉴权注解?手写全局拦截器?似乎都不是非常方便。
在这个需求中我们真正需要的是一种基于路由拦截的鉴权模式, 那么在Sa-Token怎么实现路由拦截鉴权呢? diff --git a/sa-token-doc/index.html b/sa-token-doc/index.html index 6819925e..7ef57cd8 100644 --- a/sa-token-doc/index.html +++ b/sa-token-doc/index.html @@ -4,7 +4,7 @@ Sa-Token - + diff --git a/sa-token-plugin/pom.xml b/sa-token-plugin/pom.xml index 9f71965e..580608b1 100644 --- a/sa-token-plugin/pom.xml +++ b/sa-token-plugin/pom.xml @@ -25,6 +25,7 @@ sa-token-quick-login sa-token-spring-aop sa-token-temp-jwt + sa-token-jwt diff --git a/sa-token-plugin/sa-token-jwt/.gitignore b/sa-token-plugin/sa-token-jwt/.gitignore new file mode 100644 index 00000000..f56feec7 --- /dev/null +++ b/sa-token-plugin/sa-token-jwt/.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-plugin/sa-token-jwt/pom.xml b/sa-token-plugin/sa-token-jwt/pom.xml new file mode 100644 index 00000000..a9b50c41 --- /dev/null +++ b/sa-token-plugin/sa-token-jwt/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + + cn.dev33 + sa-token-plugin + 1.27.0 + + jar + + sa-token-jwt + sa-token-jwt + sa-token-jwt + + + + + cn.dev33 + sa-token-core + ${sa-token-version} + + + + cn.hutool + hutool-all + 5.7.14 + + + + 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 new file mode 100644 index 00000000..bed4996f --- /dev/null +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java @@ -0,0 +1,235 @@ +package cn.dev33.satoken.jwt; + +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.util.SaFoxUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.jwt.JWT; +import cn.hutool.jwt.JWTException; + +/** + * jwt操作工具类封装 + * @author kong + * + */ +public class SaJwtUtil { + + /** + * key:账号类型 + */ + public static final String LOGIN_TYPE = "loginType"; + + /** + * key:账号id + */ + public static final String LOGIN_ID = "loginId"; + + /** + * key:登录设备 + */ + public static final String DEVICE = "device"; + + /** + * key:有效截止期 (时间戳) + */ + public static final String EFF = "eff"; + + /** + * 当有效期被设为此值时,代表永不过期 + */ + public static final long NEVER_EXPIRE = SaTokenDao.NEVER_EXPIRE; + + + /** + * 创建 jwt (简单方式) + * @param loginId 账号id + * @param keyt 秘钥 + * @return jwt-token + */ + public static String createToken(Object loginId, String keyt) { + + // 秘钥不可以为空 + SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); + + // 构建 + String token = JWT.create() + .setPayload(LOGIN_ID, loginId) + // 混入随机字符 + .setPayload("rn", SaFoxUtil.getRandomString(32)) + .setKey(keyt.getBytes()) + .sign(); + + // 返回 + return token; + } + + /** + * 创建 jwt (全参数方式) + * @param loginType 账号类型 + * @param loginId 账号id + * @param device 设备标识 + * @param timeout token有效期 (单位 秒) + * @param keyt 秘钥 + * @return jwt-token + */ + public static String createToken(String loginType, Object loginId, String device, long timeout, String keyt) { + + // 秘钥不可以为空 + SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); + + // 计算有效期 + long effTime = timeout; + if(timeout != NEVER_EXPIRE) { + effTime = timeout * 1000 + System.currentTimeMillis(); + } + + // 构建 + String token = JWT.create() + .setPayload(LOGIN_TYPE, loginType) + .setPayload(LOGIN_ID, loginId) + .setPayload(DEVICE, device) + .setPayload(EFF, effTime) + .setKey(keyt.getBytes()) + .sign(); + + // 返回 + return token; + } + + /** + * jwt 解析(校验签名和密码) + * @param token Jwt-Token值 + * @param keyt 秘钥 + * @return 解析后的jwt 对象 + */ + public static JWT parseToken(String token, String keyt) { + + // 如果token为null + if(token == null) { + throw NotLoginException.newInstance(null, NotLoginException.NOT_TOKEN); + } + + // 解析 + JWT jwt = null; + try { + jwt = JWT.of(token); + } catch (JWTException e) { + // 解析失败 + throw NotLoginException.newInstance(null, NotLoginException.INVALID_TOKEN, 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); + }; + + // 校验 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); + } + } + + // 返回 + return jwt; + } + + /** + * 获取 jwt 数据载荷 (校验签名和密码) + * @param token token值 + * @param keyt 秘钥 + * @return 载荷 + */ + public static JSONObject getPayloads(String token, String keyt) { + return parseToken(token, keyt).getPayloads(); + } + + /** + * 获取 jwt 数据载荷 (不校验签名和密码) + * @param token token值 + * @param keyt 秘钥 + * @return 载荷 + */ + public static JSONObject getPayloadsNotCheck(String token, String keyt) { + try { + JWT jwt = JWT.of(token); + JSONObject payloads = jwt.getPayloads(); + return payloads; + } catch (JWTException e) { + return new JSONObject(); + } + } + + /** + * 获取 jwt 代表的账号id + * @param token Token值 + * @param keyt 秘钥 + * @return 值 + */ + public static Object getLoginId(String token, String keyt) { + return getPayloads(token, keyt).get(LOGIN_ID); + } + + /** + * 获取 jwt 代表的账号id (未登录时返回null) + * @param token Token值 + * @param keyt 秘钥 + * @return 值 + */ + public static Object getLoginIdOrNull(String token, String keyt) { + try { + return getPayloads(token, keyt).get(LOGIN_ID); + } catch (NotLoginException e) { + return null; + } + } + + /** + * 获取 jwt 剩余有效期 + * @param token JwtToken值 + * @param keyt 秘钥 + * @return 值 + */ + public static long getTimeout(String token, String keyt) { + + // 如果token为null + if(token == null) { + return SaTokenDao.NOT_VALUE_EXPIRE; + } + + // 取出数据 + JWT jwt = null; + try { + jwt = JWT.of(token); + } catch (JWTException e) { + // 解析失败 + return SaTokenDao.NOT_VALUE_EXPIRE; + } + JSONObject payloads = jwt.getPayloads(); + + // 如果签名无效 + boolean verify = jwt.setKey(keyt.getBytes()).verify(); + if(verify == false) { + return SaTokenDao.NOT_VALUE_EXPIRE; + }; + + // 如果被设置为:永不过期 + Long effTime = payloads.get(EFF, Long.class); + if(effTime == NEVER_EXPIRE) { + return NEVER_EXPIRE; + } + // 如果已经超时 + if(effTime == null || effTime < System.currentTimeMillis()) { + return SaTokenDao.NOT_VALUE_EXPIRE; + } + + // 计算timeout (转化为以秒为单位的有效时间) + return (effTime - System.currentTimeMillis()) / 1000; + } + + +} 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 new file mode 100644 index 00000000..cb2b66ae --- /dev/null +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStateless.java @@ -0,0 +1,188 @@ +package cn.dev33.satoken.jwt; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.SaTokenInfo; +import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.stp.StpUtil; + +/** + * Sa-Token 整合 jwt -- stateless 无状态 + * @author kong + * + */ +public class StpLogicJwtForStateless extends StpLogic { + + /** + * 异常描述 + */ + public static final String ERROR_MESSAGE = "This API is disabled"; + + /** + * 初始化StpLogic, 并指定账号类型 + * @param loginType 账号体系标识 + */ + public StpLogicJwtForStateless() { + super(StpUtil.TYPE); + } + + /** + * 初始化StpLogic, 并指定账号类型 + * @param loginType 账号体系标识 + */ + public StpLogicJwtForStateless(String loginType) { + super(loginType); + } + + /** + * 获取jwt秘钥 + * @return / + */ + public String jwtSecretKey() { + return getConfig().getJwtSecretKey(); + } + + // + // ------ 重写方法 + // + + // ------------------- 获取token 相关 ------------------- + + /** + * 创建一个TokenValue + */ + @Override + public String createTokenValue(Object loginId) { + return SaJwtUtil.createToken(loginId, 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; + } + + // ------------------- 登录相关操作 ------------------- + + /** + * 会话登录,并指定所有登录参数Model + */ + @Override + public void login(Object id, SaLoginModel loginModel) { + + SaTokenException.throwByNull(id, "账号id不能为空"); + + // ------ 1、初始化 loginModel + loginModel.build(getConfig()); + + // ------ 2、生成一个token + String tokenValue = SaJwtUtil.createToken( + loginType, + id, + loginModel.getDeviceOrDefalut(), + loginModel.getTimeout(), + jwtSecretKey() + ); + + // 3、在当前会话写入tokenValue + setTokenValue(tokenValue, loginModel.getCookieTimeout()); + + // $$ 通知监听器,账号xxx 登录成功 + SaManager.getSaTokenListener().doLogin(loginType, id, loginModel); + } + + /** + * 获取指定Token对应的账号id (不做任何特殊处理) + */ + @Override + public String getLoginIdNotHandle(String tokenValue) { + // 先验证 loginType,如果不符,相当于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() { + // stateless模式下清除Cookie即可 + + // 如果打开了cookie模式,把cookie清除掉 + if(getConfig().getIsReadCookie() == true){ + SaManager.getSaTokenContext().getResponse().deleteCookie(getTokenName()); + } + } + + + // ------------------- 过期时间相关 ------------------- + + /** + * 获取当前登录者的 token 剩余有效时间 (单位: 秒) + */ + @Override + public long getTokenTimeout() { + return SaJwtUtil.getTimeout(getTokenValue(), jwtSecretKey()); + } + + + // ------------------- id 反查 token 相关操作 ------------------- + + /** + * 返回当前会话的登录设备 + * @return 当前令牌的登录设备 + */ + @Override + public String getLoginDevice() { + // 如果没有token,直接返回 null + String tokenValue = getTokenValue(); + if(tokenValue == null) { + return null; + } + // 如果还未登录,直接返回 null + if(!isLogin()) { + return null; + } + // 获取 + return SaJwtUtil.getPayloadsNotCheck(tokenValue, jwtSecretKey()).getStr(SaJwtUtil.DEVICE); + } + + + // ------------------- Bean对象代理 ------------------- + + /** + * 返回持久化对象 + */ + @Override + public SaTokenDao getSaTokenDao() { + throw new SaTokenException(ERROR_MESSAGE); + } + + +} diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForTokenStyle.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForTokenStyle.java new file mode 100644 index 00000000..a2a3e7b7 --- /dev/null +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForTokenStyle.java @@ -0,0 +1,49 @@ +package cn.dev33.satoken.jwt; + +import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.stp.StpUtil; + +/** + * Sa-Token 整合 jwt -- Token风格 + * @author kong + * + */ +public class StpLogicJwtForTokenStyle extends StpLogic { + + /** + * 初始化StpLogic, 并指定账号类型 + * @param loginType 账号体系标识 + */ + public StpLogicJwtForTokenStyle() { + super(StpUtil.TYPE); + } + + /** + * 初始化StpLogic, 并指定账号类型 + * @param loginType 账号体系标识 + */ + public StpLogicJwtForTokenStyle(String loginType) { + super(loginType); + } + + /** + * 获取jwt秘钥 + * @return / + */ + public String jwtSecretKey() { + return getConfig().getJwtSecretKey(); + } + + // ------ 重写方法 + + /** + * 创建一个TokenValue + * @param loginId loginId + * @return 生成的tokenValue + */ + @Override + public String createTokenValue(Object loginId) { + return SaJwtUtil.createToken(loginId, jwtSecretKey()); + } + +} diff --git a/sa-token-starter/sa-token-spring-boot-starter/src/test/java/com/pj/test/ManyLoginTest.java b/sa-token-starter/sa-token-spring-boot-starter/src/test/java/com/pj/test/ManyLoginTest.java index 8789ca0b..83813ae3 100644 --- a/sa-token-starter/sa-token-spring-boot-starter/src/test/java/com/pj/test/ManyLoginTest.java +++ b/sa-token-starter/sa-token-spring-boot-starter/src/test/java/com/pj/test/ManyLoginTest.java @@ -14,6 +14,7 @@ import cn.dev33.satoken.SaManager; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.session.TokenSign; +import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpUtil; /** @@ -160,5 +161,18 @@ public class ManyLoginTest { Assert.assertNull(StpUtil.getSessionByLoginId(10001, false)); Assert.assertNull(dao.getSession("satoken:login:session:" + 10001)); } - + + // 测试:多账号模式,在一个账号体系里登录成功,在另一个账号体系不会校验通过 + @Test + public void login7() { + SaManager.setConfig(new SaTokenConfig()); + + StpUtil.login(10001); + String token1 = StpUtil.getTokenValue(); + + StpLogic stp = new StpLogic("user"); + + Assert.assertNotNull(StpUtil.getLoginIdByToken(token1)); + Assert.assertNull(stp.getLoginIdByToken(token1)); + } }