diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/SaManager.java b/sa-token-core/src/main/java/cn/dev33/satoken/SaManager.java index ce6f24fd..8e0d108b 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/SaManager.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/SaManager.java @@ -1,8 +1,5 @@ package cn.dev33.satoken; -import java.util.LinkedHashMap; -import java.util.Map; - import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.config.SaTokenConfigFactory; import cn.dev33.satoken.context.SaTokenContext; @@ -19,7 +16,6 @@ import cn.dev33.satoken.log.SaLog; import cn.dev33.satoken.log.SaLogForConsole; import cn.dev33.satoken.same.SaSameTemplate; import cn.dev33.satoken.sign.SaSignTemplate; -import cn.dev33.satoken.sign.SaSignTemplateDefaultImpl; import cn.dev33.satoken.stp.StpInterface; import cn.dev33.satoken.stp.StpInterfaceDefaultImpl; import cn.dev33.satoken.stp.StpLogic; @@ -28,6 +24,9 @@ import cn.dev33.satoken.temp.SaTempDefaultImpl; import cn.dev33.satoken.temp.SaTempInterface; import cn.dev33.satoken.util.SaFoxUtil; +import java.util.LinkedHashMap; +import java.util.Map; + /** * 管理 Sa-Token 所有全局组件 * @author kong @@ -209,7 +208,7 @@ public class SaManager { if (saSignTemplate == null) { synchronized (SaManager.class) { if (saSignTemplate == null) { - SaManager.saSignTemplate = new SaSignTemplateDefaultImpl(); + SaManager.saSignTemplate = new SaSignTemplate(); } } } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/config/SaSignConfig.java b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaSignConfig.java new file mode 100644 index 00000000..90ca38d2 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/config/SaSignConfig.java @@ -0,0 +1,111 @@ +package cn.dev33.satoken.config; + +/** + * Sa-Token API 接口签名/验签 相关配置类 + * + * @author click33 + * @since 2023/5/2 + */ +public class SaSignConfig { + + /** + * API 调用签名秘钥 + */ + private String secretKey; + + /** + * 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距,默认15分钟 + */ + private long timestampDisparity = 1000 * 60 * 15; + + /** + * 是否校验 nonce 随机字符串 + */ + private Boolean isCheckNonce = true; + + + /** + * 获取 API 调用签名秘钥 + * + * @return / + */ + public String getSecretKey() { + return this.secretKey; + } + + /** + * 设置 API 调用签名秘钥 + * + * @param secretKey / + * @return 对象自身 + */ + public SaSignConfig setSecretKey(String secretKey) { + this.secretKey = secretKey; + return this; + } + + /** + * 获取 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距,默认15分钟 + * + * @return / + */ + public long getTimestampDisparity() { + return this.timestampDisparity; + } + + /** + * 设置 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距,默认15分钟 + * + * @param timestampDisparity / + * @return 对象自身 + */ + public SaSignConfig setTimestampDisparity(long timestampDisparity) { + this.timestampDisparity = timestampDisparity; + return this; + } + + /** + * 获取 是否校验 nonce 随机字符串 + * + * @return / + */ + public Boolean getIsCheckNonce() { + return this.isCheckNonce; + } + + /** + * 设置 是否校验 nonce 随机字符串 + * + * @param isCheckNonce / + * @return 对象自身 + */ + public SaSignConfig setIsCheckNonce(Boolean isCheckNonce) { + this.isCheckNonce = isCheckNonce; + return this; + } + + /** + * 计算保存 nonce 时应该使用的 ttl,单位:秒 + * @return / + */ + public long getSaveNonceExpire() { + // 如果 timestampDisparity >= 0,则 nonceTtl 的值等于 timestampDisparity 的值,单位转秒 + if(timestampDisparity >= 0) { + return timestampDisparity / 1000; + } + // 否则,nonceTtl 的值为 24 小时 + else { + return 60 * 60 * 24; + } + } + + @Override + public String toString() { + return "SaSignConfig [" + + "secretKey=" + secretKey + + ", timestampDisparity=" + timestampDisparity + + ", isCheckNonce=" + isCheckNonce + + "]"; + } + +} \ No newline at end of file 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 be77d923..5e82d7c4 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 @@ -1,9 +1,9 @@ package cn.dev33.satoken.config; -import java.io.Serializable; - import cn.dev33.satoken.util.SaFoxUtil; +import java.io.Serializable; + /** * Sa-Token 配置类 Model *

@@ -39,9 +39,13 @@ public class SaTokenConfig implements Serializable { */ private int maxLoginCount = 12; + /** 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用) */ + private int maxTryTimes = 12; + /** 是否尝试从请求体里读取token */ private Boolean isReadBody = true; + /** 是否尝试从header里读取token */ private Boolean isReadHeader = true; @@ -105,7 +109,12 @@ public class SaTokenConfig implements Serializable { * Cookie配置对象 */ public SaCookieConfig cookie = new SaCookieConfig(); - + + /** + * API 签名配置对象 + */ + public SaSignConfig sign = new SaSignConfig(); + /** * @return token名称 (同时也是cookie名称) @@ -205,6 +214,22 @@ public class SaTokenConfig implements Serializable { return this; } + /** + * @return 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用) + */ + public int getMaxTryTimes() { + return maxTryTimes; + } + + /** + * @param maxTryTimes 在每次创建 token 时的最高循环次数,用于保证 token 唯一性(-1=不循环尝试,直接使用) + * @return 对象自身 + */ + public SaTokenConfig setMaxTryTimes(int maxTryTimes) { + this.maxTryTimes = maxTryTimes; + return this; + } + /** * @return 是否尝试从请求体里读取token */ @@ -411,7 +436,7 @@ public class SaTokenConfig implements Serializable { * @param logLevelInt 日志等级 int 值(1=trace、2=debug、3=info、4=warn、5=error、6=fatal) * @return 对象自身 */ - public SaTokenConfig setLogLeveInt(int logLevelInt) { + public SaTokenConfig setLogLevelInt(int logLevelInt) { this.logLevelInt = logLevelInt; this.logLevel = SaFoxUtil.translateLogLevelToString(logLevelInt); return this; @@ -512,7 +537,23 @@ public class SaTokenConfig implements Serializable { this.cookie = cookie; return this; } - + + /** + * @return API 签名全局配置对象 + */ + public SaSignConfig getSign() { + return sign; + } + + /** + * @param sign API 签名全局配置对象 + * @return 对象自身 + */ + public SaTokenConfig setSign(SaSignConfig sign) { + this.sign = sign; + return this; + } + @Override public String toString() { return "SaTokenConfig [" @@ -521,7 +562,8 @@ public class SaTokenConfig implements Serializable { + ", activityTimeout=" + activityTimeout + ", isConcurrent=" + isConcurrent + ", isShare=" + isShare - + ", maxLoginCount=" + maxLoginCount + + ", maxLoginCount=" + maxLoginCount + + ", maxTryTimes=" + maxTryTimes + ", isReadBody=" + isReadBody + ", isReadHeader=" + isReadHeader + ", isReadCookie=" + isReadCookie @@ -540,69 +582,9 @@ public class SaTokenConfig implements Serializable { + ", currDomain=" + currDomain + ", sameTokenTimeout=" + sameTokenTimeout + ", checkSameToken=" + checkSameToken - + ", cookie=" + cookie + + ", cookie=" + cookie + + ", sign=" + sign + "]"; } - - /** - *

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

- * @return 是否尝试从header里读取token - */ - @Deprecated - public Boolean getIsReadHead() { - return isReadHeader; - } - - /** - *

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

- * @param isReadHead 是否尝试从header里读取token - * @return 对象自身 - */ - @Deprecated - public SaTokenConfig setIsReadHead(Boolean isReadHead) { - this.isReadHeader = isReadHead; - return this; - } - - /** - *

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

- * @return Id-Token的有效期 (单位: 秒) - */ - @Deprecated - public long getIdTokenTimeout() { - return sameTokenTimeout; - } - - /** - *

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

- * @param idTokenTimeout Id-Token的有效期 (单位: 秒) - * @return 对象自身 - */ - @Deprecated - public SaTokenConfig setIdTokenTimeout(long idTokenTimeout) { - this.sameTokenTimeout = idTokenTimeout; - return this; - } - - /** - *

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

- * @return 是否校验Id-Token(部分rpc插件有效) - */ - @Deprecated - public Boolean getCheckIdToken() { - return checkSameToken; - } - - /** - *

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

- * @param checkIdToken 是否校验Id-Token(部分rpc插件有效) - * @return 对象自身 - */ - @Deprecated - public SaTokenConfig setCheckIdToken(Boolean checkIdToken) { - this.checkSameToken = checkIdToken; - return this; - } - } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java b/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java index 12e92aa3..8dfb402e 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java @@ -4,6 +4,9 @@ import cn.dev33.satoken.error.SaErrorCode; import cn.dev33.satoken.exception.SaTokenException; import cn.dev33.satoken.util.SaFoxUtil; +import java.util.List; +import java.util.Map; + /** * Request 包装类 * @author kong @@ -70,8 +73,19 @@ public interface SaRequest { } return paramValue; } - - + + /** + * 获取 [请求体] 里提交的所有参数名称 + * @return 参数名称列表 + */ + public List getParamNames(); + + /** + * 获取 [请求体] 里提交的所有参数 + * @return 参数列表 + */ + public Map getParamMap(); + /** * 在 [请求头] 里获取一个值 * @param name 键 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaSignException.java b/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaSignException.java new file mode 100644 index 00000000..491bed46 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaSignException.java @@ -0,0 +1,48 @@ +package cn.dev33.satoken.exception; + +import cn.dev33.satoken.util.SaFoxUtil; + +/** + * 一个异常:代表 API 参数签名校验失败 + * + * @author kong + * @since 2023-5-3 + */ +public class SaSignException extends SaTokenException { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129545290130144L; + + /** + * 一个异常:代表 API 参数签名校验失败 + * @param message 异常描述 + */ + public SaSignException(String message) { + super(message); + } + + /** + * 如果flag==true,则抛出message异常 + * @param flag 标记 + * @param message 异常信息 + */ + public static void throwBy(boolean flag, String message) { + if(flag) { + throw new SaSignException(message); + } + } + + /** + * 如果 value isEmpty,则抛出 message 异常 + * @param value 值 + * @param message 异常信息 + */ + public static void throwByNull(Object value, String message) { + if(SaFoxUtil.isEmpty(value)) { + throw new SaSignException(message); + } + } + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/fun/SaGenerateUniqueTokenFunction.java b/sa-token-core/src/main/java/cn/dev33/satoken/fun/SaGenerateUniqueTokenFunction.java new file mode 100644 index 00000000..76f4cc6d --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/fun/SaGenerateUniqueTokenFunction.java @@ -0,0 +1,31 @@ +package cn.dev33.satoken.fun; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * 生成唯一式 token 的方法签名 + * + * @author click33 + * @since 2023/4/30 + */ +@FunctionalInterface +public interface SaGenerateUniqueTokenFunction { + + /** + * 封装 token 生成、校验的代码,生成唯一式 token + * + * @param elementName 要生成的元素名称,方便抛出异常时组织提示信息 + * @param maxTryTimes 最大尝试次数 + * @param createTokenFunction 创建 token 的函数 + * @param checkTokenFunction 校验 token 是否唯一的函数(返回 true 表示唯一,可用) + * @return 最终生成的唯一式 token + */ + public String execute( + String elementName, + int maxTryTimes, + Supplier createTokenFunction, + Function checkTokenFunction + ); + +} \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java index 1e9107df..4bb1890a 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenEventCenter.java @@ -262,12 +262,12 @@ public class SaTokenEventCenter { /** * 全局组件载入 - * @param comtName 组件名称 - * @param comtObj 组件对象 + * @param compName 组件名称 + * @param compObj 组件对象 */ - public static void doRegisterComponent(String comtName, Object comtObj) { + public static void doRegisterComponent(String compName, Object compObj) { for (SaTokenListener listener : listenerList) { - listener.doRegisterComponent(comtName, comtObj); + listener.doRegisterComponent(compName, compObj); } } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java index e71b4700..6cb6ffe8 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListener.java @@ -103,10 +103,10 @@ public interface SaTokenListener { /** * 全局组件载入 - * @param comtName 组件名称 - * @param comtObj 组件对象 + * @param compName 组件名称 + * @param compObj 组件对象 */ - public default void doRegisterComponent(String comtName, Object comtObj) {} + public default void doRegisterComponent(String compName, Object compObj) {} /** * StpLogic 对象替换 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java index 9ef0a2da..8b763056 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/listener/SaTokenListenerForLog.java @@ -106,13 +106,13 @@ public class SaTokenListenerForLog implements SaTokenListener { /** * 全局组件载入 - * @param comtName 组件名称 - * @param comtObj 组件对象 + * @param compName 组件名称 + * @param compObj 组件对象 */ @Override - public void doRegisterComponent(String comtName, Object comtObj) { - String canonicalName = comtObj == null ? null : comtObj.getClass().getCanonicalName(); - log.info("全局组件 {} 载入成功: {}", comtName, canonicalName); + public void doRegisterComponent(String compName, Object compObj) { + String canonicalName = compObj == null ? null : compObj.getClass().getCanonicalName(); + log.info("全局组件 {} 载入成功: {}", compName, canonicalName); } /** diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplate.java b/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplate.java index 0635105c..c9057058 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplate.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplate.java @@ -1,33 +1,91 @@ package cn.dev33.satoken.sign; +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.config.SaSignConfig; +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.error.SaErrorCode; +import cn.dev33.satoken.exception.SaSignException; +import cn.dev33.satoken.secure.SaSecureUtil; +import cn.dev33.satoken.util.SaFoxUtil; + import java.util.Map; import java.util.TreeMap; -import cn.dev33.satoken.error.SaErrorCode; -import cn.dev33.satoken.exception.SaTokenException; -import cn.dev33.satoken.secure.SaSecureUtil; -import cn.dev33.satoken.util.SaFoxUtil; - /** - * 参数签名算法 - * + * API 参数签名算法 + * * @author kong - * @since: 2022-4-27 + * @since 2022-4-27 */ -public interface SaSignTemplate { +public class SaSignTemplate { + + // ----------- 签名配置 + + SaSignConfig signConfig; + + /** + * 获取:API 签名配置 + * @return / + */ + public SaSignConfig getSignConfig() { + return signConfig; + } + + /** + * 获取:API 签名配置: + * 1. 如果用户自定义了 signConfig ,则使用用户自定义的。 + * 2. 否则使用全局默认配置。 + * @return / + */ + public SaSignConfig getSignConfigOrGlobal() { + // 如果用户自定义了 signConfig ,则使用用户自定义的 + if(signConfig != null) { + return signConfig; + } + // 否则使用全局默认配置 + return SaManager.getConfig().getSign(); + } + + /** + * 获取:API 签名配置的秘钥 + * @return / + */ + public String getSecretKey() { + return getSignConfigOrGlobal().getSecretKey(); + } + + /** + * 设置:API 签名配置 + * @param signConfig / + */ + public SaSignTemplate setSignConfig(SaSignConfig signConfig) { + this.signConfig = signConfig; + return this; + } + + + // ----------- 自定义使用的参数名称 (不声明final,允许开发者自定义修改) + + public static String key = "key"; + public static String timestamp = "timestamp"; + public static String nonce = "nonce"; + public static String sign = "sign"; + + + // ----------- 拼接参数 /** * 将所有参数连接成一个字符串(不排序),形如:b=28a=18c=3 * @param paramsMap 参数列表 * @return 拼接出的参数字符串 */ - public default String joinParams(Map paramsMap) { + public String joinParams(Map paramsMap) { // 按照 k1=v1&k2=v2&k3=v3 排列 StringBuilder sb = new StringBuilder(); for (String key : paramsMap.keySet()) { Object value = paramsMap.get(key); - if(SaFoxUtil.isEmpty(value) == false) { + if( ! SaFoxUtil.isEmpty(value) ) { sb.append(key).append("=").append(value).append("&"); } } @@ -46,9 +104,9 @@ public interface SaSignTemplate { * @param paramsMap 参数列表 * @return 拼接出的参数字符串 */ - public default String joinParamsDictSort(Map paramsMap) { + public String joinParamsDictSort(Map paramsMap) { // 保证字段按照字典顺序排列 - if(paramsMap instanceof TreeMap == false) { + if( ! (paramsMap instanceof TreeMap) ) { paramsMap = new TreeMap<>(paramsMap); } @@ -56,54 +114,40 @@ public interface SaSignTemplate { return joinParams(paramsMap); } + + // ----------- 创建签名 + /** * 创建签名:md5(paramsStr + keyStr) - * @param paramsMap 参数列表 - * @param key 秘钥 + * @param paramsMap 参数列表 * @return 签名 */ - public default String createSign(Map paramsMap, String key) { - SaTokenException.throwByNull(key, "参与参数签名的秘钥不可为空", SaErrorCode.CODE_12201); - + public String createSign(Map paramsMap) { + String secretKey = getSecretKey(); + SaSignException.throwByNull(secretKey, "参与参数签名的秘钥不可为空", SaErrorCode.CODE_12201); + + // 如果调用者不小心传入了 sign 参数,则此处需要将 sign 参数排除在外 + if(paramsMap.containsKey(sign)) { + // 为了保证不影响原有的 paramsMap,此处需要再复制一份 + paramsMap = new TreeMap<>(paramsMap); + paramsMap.remove(sign); + } + + // 计算签名 String paramsStr = joinParamsDictSort(paramsMap); - String fullStr = paramsStr + "&key=" + key; + String fullStr = paramsStr + "&" + key + "=" + secretKey; return SaSecureUtil.md5(fullStr); } - /** - * 判断:给定的参数 + 秘钥 生成的签名是否为有效签名 - * @param paramsMap 参数列表 - * @param key 秘钥 - * @param sign 待验证的签名 - * @return 签名是否有效 - */ - public default boolean isValidSign(Map paramsMap, String key, String sign) { - String theSign = createSign(paramsMap, key); - return theSign.equals(sign); - } - - /** - * 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常 - * @param paramsMap 参数列表 - * @param key 秘钥 - * @param sign 待验证的签名 - */ - public default void checkSign(Map paramsMap, String key, String sign) { - if(isValidSign(paramsMap, key, sign) == false) { - throw new SaTokenException("无效签名:" + sign).setCode(SaErrorCode.CODE_12202); - } - } - /** * 给 paramsMap 追加 timestamp、nonce、sign 三个参数 - * @param paramsMap 参数列表 - * @param key 秘钥 + * @param paramsMap 参数列表 * @return 加工后的参数列表 */ - public default Map addSignParams(Map paramsMap, String key) { - paramsMap.put("timestamp", String.valueOf(System.currentTimeMillis())); - paramsMap.put("nonce", SaFoxUtil.getRandomString(32)); - paramsMap.put("sign", createSign(paramsMap, key)); + public Map addSignParams(Map paramsMap) { + paramsMap.put(timestamp, String.valueOf(System.currentTimeMillis())); + paramsMap.put(nonce, SaFoxUtil.getRandomString(32)); + paramsMap.put(sign, createSign(paramsMap)); return paramsMap; } @@ -111,24 +155,26 @@ public interface SaSignTemplate { * 给 paramsMap 追加 timestamp、nonce、sign 三个参数,并转换为参数字符串,形如: * data=xxx8nonce=xxx8timestamp=xxx8sign=xxx * @param paramsMap 参数列表 - * @param key 秘钥 - * @return 加工后的参数列表 转化为的参数字符串 + * @return 加工后的参数列表 转化为的参数字符串 */ - public default String addSignParamsToString(Map paramsMap, String key) { - // 追加参数 - paramsMap = addSignParams(paramsMap, key); - - // . - return joinParams(paramsMap); + public String addSignParamsAndJoin(Map paramsMap) { + // 追加参数 + paramsMap = addSignParams(paramsMap); + + // 拼接参数 + return joinParams(paramsMap); } + + // ----------- 校验签名 + /** * 判断:指定时间戳与当前时间戳的差距是否在允许的范围内 * @param timestamp 待校验的时间戳 - * @param allowDisparity 允许的最大时间差(单位:ms),-1 代表不限制 * @return 是否在允许的范围内 */ - public default boolean isValidTimestamp(long timestamp, long allowDisparity) { + public boolean isValidTimestamp(long timestamp) { + long allowDisparity = getSignConfigOrGlobal().getTimestampDisparity(); long disparity = Math.abs(System.currentTimeMillis() - timestamp); return allowDisparity == -1 || disparity <= allowDisparity; } @@ -136,12 +182,142 @@ public interface SaSignTemplate { /** * 校验:指定时间戳与当前时间戳的差距是否在允许的范围内,如果超出则抛出异常 * @param timestamp 待校验的时间戳 - * @param allowDisparity 允许的最大时间差(单位:ms),-1 代表不限制 */ - public default void checkTimestamp(long timestamp, long allowDisparity) { - if(isValidTimestamp(timestamp, allowDisparity) == false) { - throw new SaTokenException("timestamp 超出允许的范围:" + timestamp).setCode(SaErrorCode.CODE_12203); + public void checkTimestamp(long timestamp) { + if( ! isValidTimestamp(timestamp) ) { + throw new SaSignException("timestamp 超出允许的范围:" + timestamp).setCode(SaErrorCode.CODE_12203); } } + /** + * 判断:随机字符串 nonce 是否有效。 + * 注意:同一 nonce 可以被多次判断有效,不会被缓存 + * @param nonce 待判断的随机字符串 + * @return 是否有效 + */ + public boolean isValidNonce(String nonce) { + // 为空代表无效 + if(SaFoxUtil.isEmpty(nonce)) { + return false; + } + + // 校验此 nonce 是否已被使用过 + String key = splicingNonceSaveKey(nonce); + return SaManager.getSaTokenDao().get(key) == null; + } + + /** + * 校验:随机字符串 nonce 是否有效,如果无效则抛出异常。 + * 注意:同一 nonce 只可以被校验通过一次,校验后将保存在缓存中,再次校验将无法通过 + * @param nonce 待校验的随机字符串 + */ + public void checkNonce(String nonce) { + // 为空代表无效 + if(SaFoxUtil.isEmpty(nonce)) { + throw new SaSignException("nonce 为空,无效"); + } + + // 校验此 nonce 是否已被使用过 + String key = splicingNonceSaveKey(nonce); + if(SaManager.getSaTokenDao().get(key) != null) { + throw new SaSignException("此 nonce 已被使用过,不可重复使用:" + nonce); + } + + // 校验通过后,将此 nonce 保存在缓存中,保证下次校验无法通过 + SaManager.getSaTokenDao().set(key, nonce, getSignConfigOrGlobal().getSaveNonceExpire()); + } + + /** + * 判断:给定的参数 + 秘钥 生成的签名是否为有效签名 + * @param paramsMap 参数列表 + * @param sign 待验证的签名 + * @return 签名是否有效 + */ + public boolean isValidSign(Map paramsMap, String sign) { + String theSign = createSign(paramsMap); + return theSign.equals(sign); + } + + /** + * 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常 + * @param paramsMap 参数列表 + * @param sign 待验证的签名 + */ + public void checkSign(Map paramsMap, String sign) { + if( ! isValidSign(paramsMap, sign) ) { + throw new SaSignException("无效签名:" + sign).setCode(SaErrorCode.CODE_12202); + } + } + + /** + * 判断:参数列表中的 nonce、timestamp、sign 是否均为合法的 + * @param paramMap 待校验的请求参数集合 + * @return 是否合法 + */ + public boolean isValidParamMap(Map paramMap) { + // 获取必须的三个参数 + String timestampValue = paramMap.get(timestamp); + String nonceValue = paramMap.get(nonce); + String signValue = paramMap.get(sign); + + // 三个参数必须全部非空 + SaSignException.throwByNull(timestampValue, "缺少 timestamp 字段"); + SaSignException.throwByNull(nonceValue, "缺少 nonce 字段"); + SaSignException.throwByNull(signValue, "缺少 sign 字段"); + + // 三个值的校验必须全部通过 + return isValidTimestamp(Long.parseLong(timestampValue)) + && (getSignConfigOrGlobal().getIsCheckNonce() ? isValidNonce(nonceValue) : true) + && isValidSign(paramMap, signValue); + } + + /** + * 校验:参数列表中的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常 + * @param paramMap 待校验的请求参数集合 + */ + public void checkParamMap(Map paramMap) { + // 获取必须的三个参数 + String timestampValue = paramMap.get(timestamp); + String nonceValue = paramMap.get(nonce); + String signValue = paramMap.get(sign); + + // 依次校验三个参数 + checkTimestamp(Long.parseLong(timestampValue)); + if(getSignConfigOrGlobal().getIsCheckNonce()) { + checkNonce(nonceValue); + } + checkSign(paramMap, signValue); + + // 通过 √ + } + + /** + * 判断:一个请求中的 nonce、timestamp、sign 是否均为合法的 + * @param request 待校验的请求对象 + * @return 是否合法 + */ + public boolean isValidRequest(SaRequest request) { + return isValidParamMap(request.getParamMap()); + } + + /** + * 校验:一个请求的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常 + * @param request 待校验的请求对象 + */ + public void checkRequest(SaRequest request) { + checkParamMap(request.getParamMap()); + } + + + // ------------------- 返回相应key ------------------- + + /** + * 拼接key:存储 nonce 时使用的 key + * @param nonce nonce 值 + * @return key + */ + public String splicingNonceSaveKey(String nonce) { + return SaManager.getConfig().getTokenName() + ":sign:nonce:" + nonce; + } + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplateDefaultImpl.java b/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplateDefaultImpl.java deleted file mode 100644 index f8137033..00000000 --- a/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplateDefaultImpl.java +++ /dev/null @@ -1,11 +0,0 @@ -package cn.dev33.satoken.sign; - -/** - * 参数签名算法 [默认实现类] - * - * @author kong - * @since: 2022-4-27 - */ -public class SaSignTemplateDefaultImpl implements SaSignTemplate { - -} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignUtil.java new file mode 100644 index 00000000..649b9b2c --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignUtil.java @@ -0,0 +1,159 @@ +package cn.dev33.satoken.sign; + +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.context.model.SaRequest; + +import java.util.Map; + +/** + * API 参数签名算法 - 工具类 + * + * @author kong + * @since 2022-4-27 + */ +public class SaSignUtil { + + // ----------- 拼接参数 + + /** + * 将所有参数连接成一个字符串(不排序),形如:b=28a=18c=3 + * @param paramsMap 参数列表 + * @return 拼接出的参数字符串 + */ + public static String joinParams(Map paramsMap) { + return SaManager.getSaSignTemplate().joinParams(paramsMap); + } + + /** + * 将所有参数按照字典顺序连接成一个字符串,形如:a=18b=28c=3 + * @param paramsMap 参数列表 + * @return 拼接出的参数字符串 + */ + public static String joinParamsDictSort(Map paramsMap) { + return SaManager.getSaSignTemplate().joinParamsDictSort(paramsMap); + } + + + // ----------- 创建签名 + + /** + * 创建签名:md5(paramsStr + keyStr) + * @param paramsMap 参数列表 + * @return 签名 + */ + public static String createSign(Map paramsMap) { + return SaManager.getSaSignTemplate().createSign(paramsMap); + } + + /** + * 给 paramsMap 追加 timestamp、nonce、sign 三个参数 + * @param paramsMap 参数列表 + * @return 加工后的参数列表 + */ + public static Map addSignParams(Map paramsMap) { + return SaManager.getSaSignTemplate().addSignParams(paramsMap); + } + + /** + * 给 paramsMap 追加 timestamp、nonce、sign 三个参数,并转换为参数字符串,形如: + * data=xxx8nonce=xxx8timestamp=xxx8sign=xxx + * @param paramsMap 参数列表 + * @return 加工后的参数列表 转化为的参数字符串 + */ + public static String addSignParamsAndJoin(Map paramsMap) { + return SaManager.getSaSignTemplate().addSignParamsAndJoin(paramsMap); + } + + + // ----------- 校验签名 + + /** + * 判断:指定时间戳与当前时间戳的差距是否在允许的范围内 + * @param timestamp 待校验的时间戳 + * @return 是否在允许的范围内 + */ + public static boolean isValidTimestamp(long timestamp) { + return SaManager.getSaSignTemplate().isValidTimestamp(timestamp); + } + + /** + * 校验:指定时间戳与当前时间戳的差距是否在允许的范围内,如果超出则抛出异常 + * @param timestamp 待校验的时间戳 + */ + public static void checkTimestamp(long timestamp) { + SaManager.getSaSignTemplate().checkTimestamp(timestamp); + } + + /** + * 判断:随机字符串 nonce 是否有效。 + * 注意:同一 nonce 可以被多次判断有效,不会被缓存 + * @param nonce 待判断的随机字符串 + * @return 是否有效 + */ + public static boolean isValidNonce(String nonce) { + return SaManager.getSaSignTemplate().isValidNonce(nonce); + } + + /** + * 校验:随机字符串 nonce 是否有效,如果无效则抛出异常。 + * 注意:同一 nonce 只可以被校验通过一次,校验后将保存在缓存中,再次校验将无法通过 + * @param nonce 待校验的随机字符串 + */ + public static void checkNonce(String nonce) { + SaManager.getSaSignTemplate().checkNonce(nonce); + } + + /** + * 判断:给定的参数 + 秘钥 生成的签名是否为有效签名 + * @param paramsMap 参数列表 + * @param sign 待验证的签名 + * @return 签名是否有效 + */ + public static boolean isValidSign(Map paramsMap, String sign) { + return SaManager.getSaSignTemplate().isValidSign(paramsMap, sign); + } + + /** + * 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常 + * @param paramsMap 参数列表 + * @param sign 待验证的签名 + */ + public static void checkSign(Map paramsMap, String sign) { + SaManager.getSaSignTemplate().checkSign(paramsMap, sign); + } + + /** + * 判断:参数列表中的 nonce、timestamp、sign 是否均为合法的 + * @param paramMap 待校验的请求参数集合 + * @return 是否合法 + */ + public static boolean isValidParamMap(Map paramMap) { + return SaManager.getSaSignTemplate().isValidParamMap(paramMap); + } + + /** + * 校验:参数列表中的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常 + * @param paramMap 待校验的请求参数集合 + */ + public static void checkParamMap(Map paramMap) { + SaManager.getSaSignTemplate().checkParamMap(paramMap); + } + + /** + * 判断:一个请求中的 nonce、timestamp、sign 是否均为合法的 + * @param request 待校验的请求对象 + * @return 是否合法 + */ + public static boolean isValidRequest(SaRequest request) { + return SaManager.getSaSignTemplate().isValidRequest(request); + } + + /** + * 校验:一个请求的 nonce、timestamp、sign 是否均为合法的,如果不合法,则抛出对应的异常 + * @param request 待校验的请求对象 + */ + public static void checkRequest(SaRequest request) { + SaManager.getSaSignTemplate().checkRequest(request); + } + +} 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 6197e7b5..09ea2826 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 @@ -351,7 +351,7 @@ public class StpLogic { * @return 返回会话令牌 */ public String createLoginSession(Object id, SaLoginModel loginModel) { - + // ------ 前置检查 SaTokenException.throwByNull(id, "账号id不能为空", SaErrorCode.CODE_11002); @@ -395,7 +395,7 @@ public class StpLogic { * @return 返回 Token */ protected String distUsableToken(Object id, SaLoginModel loginModel) { - + // 获取全局配置 Boolean isConcurrent = getConfig().getIsConcurrent(); @@ -421,8 +421,17 @@ public class StpLogic { } } - // 如果代码走到此处,说明未能成功复用旧Token,需要新建Token - return createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData()); + // 如果代码走到此处,说明未能成功复用旧Token,需要新建Token + return SaStrategy.me.generateUniqueToken.execute( + "token", + getConfigOfMaxTryTimes(), + () -> { + return createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData()); + }, + tokenValue -> { + return getLoginIdNotHandle(tokenValue) == null; + } + ); } // --- 注销 @@ -1036,7 +1045,17 @@ public class StpLogic { */ if(isCreate) { // 随机创建一个 Token - tokenValue = createTokenValue(null, null, getConfig().getTimeout(), null); + tokenValue = SaStrategy.me.generateUniqueToken.execute( + "token", + getConfigOfMaxTryTimes(), + () -> { + return createTokenValue(null, null, getConfig().getTimeout(), null); + }, + token -> { + return getTokenSessionByToken(token, false) == null; + } + ); + // 写入 [最后操作时间] setLastActivityToNow(tokenValue); // 在当前上下文写入此 TokenValue @@ -1382,7 +1401,15 @@ public class StpLogic { * @param roleArray 角色标识数组 */ public void checkRoleAnd(String... roleArray){ - Object loginId = getLoginId(); + // 先获取当前是哪个账号id + Object loginId = getLoginId(); + + // 如果没有指定权限,那么直接跳过 + if(roleArray == null || roleArray.length == 0) { + return; + } + + // 开始校验 List roleList = getRoleList(loginId); for (String role : roleArray) { if(!hasElement(roleList, role)) { @@ -1396,7 +1423,15 @@ public class StpLogic { * @param roleArray 角色标识数组 */ public void checkRoleOr(String... roleArray){ - Object loginId = getLoginId(); + // 先获取当前是哪个账号id + Object loginId = getLoginId(); + + // 如果没有指定权限,那么直接跳过 + if(roleArray == null || roleArray.length == 0) { + return; + } + + // 开始校验 List roleList = getRoleList(loginId); for (String role : roleArray) { if(hasElement(roleList, role)) { @@ -1404,9 +1439,9 @@ public class StpLogic { return; } } - if(roleArray.length > 0) { - throw new NotRoleException(roleArray[0], this.loginType).setCode(SaErrorCode.CODE_11041); - } + + // 代码至此,说明一个都没通过,需要抛出无角色异常 + throw new NotRoleException(roleArray[0], this.loginType).setCode(SaErrorCode.CODE_11041); } @@ -1495,7 +1530,15 @@ public class StpLogic { * @param permissionArray 权限码数组 */ public void checkPermissionAnd(String... permissionArray){ - Object loginId = getLoginId(); + // 先获取当前是哪个账号id + Object loginId = getLoginId(); + + // 如果没有指定权限,那么直接跳过 + if(permissionArray == null || permissionArray.length == 0) { + return; + } + + // 开始校验 List permissionList = getPermissionList(loginId); for (String permission : permissionArray) { if(!hasElement(permissionList, permission)) { @@ -1509,7 +1552,15 @@ public class StpLogic { * @param permissionArray 权限码数组 */ public void checkPermissionOr(String... permissionArray){ - Object loginId = getLoginId(); + // 先获取当前是哪个账号id + Object loginId = getLoginId(); + + // 如果没有指定权限,那么直接跳过 + if(permissionArray == null || permissionArray.length == 0) { + return; + } + + // 开始校验 List permissionList = getPermissionList(loginId); for (String permission : permissionArray) { if(hasElement(permissionList, permission)) { @@ -1517,9 +1568,9 @@ public class StpLogic { return; } } - if(permissionArray.length > 0) { - throw new NotPermissionException(permissionArray[0], this.loginType).setCode(SaErrorCode.CODE_11051); - } + + // 代码至此,说明一个都没通过,需要抛出无权限异常 + throw new NotPermissionException(permissionArray[0], this.loginType).setCode(SaErrorCode.CODE_11051); } @@ -2273,6 +2324,14 @@ public class StpLogic { return (int) timeout; } + /** + * 返回全局配置的 maxTryTimes 值,在每次创建 token 时,对其唯一性测试的最高次数(-1=不测试) + * @return / + */ + public int getConfigOfMaxTryTimes() { + return getConfig().getMaxTryTimes(); + } + /** * 返回持久化对象 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java b/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java index 56186e97..4b0095dd 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java @@ -1,5 +1,14 @@ package cn.dev33.satoken.strategy; +import cn.dev33.satoken.SaManager; +import cn.dev33.satoken.annotation.*; +import cn.dev33.satoken.basic.SaBasicUtil; +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.fun.SaGenerateUniqueTokenFunction; +import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.util.SaFoxUtil; +import cn.dev33.satoken.util.SaTokenConsts; + import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; @@ -8,18 +17,7 @@ import java.util.UUID; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; - -import cn.dev33.satoken.SaManager; -import cn.dev33.satoken.annotation.SaCheckBasic; -import cn.dev33.satoken.annotation.SaCheckDisable; -import cn.dev33.satoken.annotation.SaCheckLogin; -import cn.dev33.satoken.annotation.SaCheckPermission; -import cn.dev33.satoken.annotation.SaCheckRole; -import cn.dev33.satoken.annotation.SaCheckSafe; -import cn.dev33.satoken.basic.SaBasicUtil; -import cn.dev33.satoken.session.SaSession; -import cn.dev33.satoken.util.SaFoxUtil; -import cn.dev33.satoken.util.SaTokenConsts; +import java.util.function.Supplier; /** * Sa-Token 策略对象 @@ -47,9 +45,7 @@ public final class SaStrategy { */ public static final SaStrategy me = new SaStrategy(); - // - // 所有策略 - // + // ----------------------- 所有策略 /** * 创建 Token 的策略 @@ -194,11 +190,38 @@ public final class SaStrategy { me.getAnnotation.apply(method.getDeclaringClass(), annotationClass) != null; }; + /** + * 生成唯一式 token 的算法 + *

参数 [元素名称, 最大尝试次数, 创建 token 函数, 检查 token 函数] + */ + public SaGenerateUniqueTokenFunction generateUniqueToken = (elementName, maxTryTimes, createTokenFunction, checkTokenFunction) -> { - // - // 重写策略 set连缀风格 - // - + // 为方便叙述,以下代码注释均假设在处理生成 token 的场景,但实际上本方法也可能被用于生成 code、ticket 等 + + // 循环生成 + for (int i = 1; ; i++) { + // 生成 token + String token = createTokenFunction.get(); + + // 如果 maxTryTimes == -1,表示不做唯一性验证,直接返回 + if (maxTryTimes == -1) { + return token; + } + + // 如果 token 在DB库查询不到数据,说明是个可用的全新 token,直接返回 + if (checkTokenFunction.apply(token)) { + return token; + } + + // 如果已经循环了 maxTryTimes 次,仍然没有创建出可用的 token,那么抛出异常 + if (i >= maxTryTimes) { + throw new SaTokenException(elementName + " 生成失败,已尝试" + i + "次,生成算法过于简单或资源池已耗尽"); + } + } + }; + + + // ----------------------- 重写策略 set连缀风格 /** * 重写创建 Token 的策略 @@ -276,5 +299,17 @@ public final class SaStrategy { this.isAnnotationPresent = isAnnotationPresent; return this; } - + + /** + * 生成唯一式 token 的算法 + *

参数 [元素名称, 最大尝试次数, 创建 token 函数, 检查 token 函数] + * + * @param generateUniqueToken / + * @return 对象自身 + */ + public SaStrategy setGenerateUniqueToken(SaGenerateUniqueTokenFunction generateUniqueToken) { + this.generateUniqueToken = generateUniqueToken; + return this; + } + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java index 40edc4d0..4dc0509f 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java @@ -1,5 +1,8 @@ package cn.dev33.satoken.util; +import cn.dev33.satoken.error.SaErrorCode; +import cn.dev33.satoken.exception.SaTokenException; + import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; @@ -8,19 +11,10 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Pattern; -import cn.dev33.satoken.error.SaErrorCode; -import cn.dev33.satoken.exception.SaTokenException; - /** * Sa-Token 内部工具类 * diff --git a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/SaTokenConfigure.java index 9ef97a41..7f41b5fb 100644 --- a/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/SaTokenConfigure.java +++ b/sa-token-demo/sa-token-demo-case/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -94,7 +94,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { return SaResult.error(e.getMessage()); }) - // 前置函数:在每次认证函数之前执行 + // 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入) .setBeforeAuth(r -> { // ---------- 设置一些安全响应头 ---------- SaHolder.getResponse() diff --git a/sa-token-demo/sa-token-demo-jwt/pom.xml b/sa-token-demo/sa-token-demo-jwt/pom.xml index 8f184e81..9e200919 100644 --- a/sa-token-demo/sa-token-demo-jwt/pom.xml +++ b/sa-token-demo/sa-token-demo-jwt/pom.xml @@ -42,7 +42,7 @@ - + diff --git a/sa-token-demo/sa-token-demo-solon/pom.xml b/sa-token-demo/sa-token-demo-solon/pom.xml index b2f65adf..87289c56 100644 --- a/sa-token-demo/sa-token-demo-solon/pom.xml +++ b/sa-token-demo/sa-token-demo-solon/pom.xml @@ -13,7 +13,7 @@ 2.2.3 - + 1.34.0 diff --git a/sa-token-demo/sa-token-demo-solon/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-solon/src/main/java/com/pj/satoken/SaTokenConfigure.java index 72a3c559..567489d5 100644 --- a/sa-token-demo/sa-token-demo-solon/src/main/java/com/pj/satoken/SaTokenConfigure.java +++ b/sa-token-demo/sa-token-demo-solon/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -42,7 +42,7 @@ public class SaTokenConfigure { return AjaxJson.getError(e.getMessage()); }) - // 前置函数:在每次认证函数之前执行 + // 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入) .setBeforeAuth(r -> { // ---------- 设置一些安全响应头 ---------- SaHolder.getResponse() diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java index 4f5e4467..7c01ff54 100644 --- a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/SaTokenDemoApplication.java @@ -1,10 +1,9 @@ package com.pj; +import cn.dev33.satoken.SaManager; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import cn.dev33.satoken.SaManager; - /** * Sa-Token整合SpringBoot 示例,整合redis * @author kong diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/GlobalException.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/GlobalException.java index 7ee67369..1d6ebb1c 100644 --- a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/GlobalException.java +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/current/GlobalException.java @@ -26,7 +26,7 @@ public class GlobalException { // 打印堆栈,以供调试 System.out.println("全局异常---------------"); - e.printStackTrace(); + e.printStackTrace(); // 不同异常返回不同状态码 AjaxJson aj = null; diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java index a4a4995e..74e61a58 100644 --- a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -1,14 +1,13 @@ 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.context.SaHolder; import cn.dev33.satoken.filter.SaServletFilter; import cn.dev33.satoken.interceptor.SaInterceptor; import cn.dev33.satoken.util.SaResult; +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; /** @@ -24,7 +23,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { */ @Override public void addInterceptors(InterceptorRegistry registry) { - // 注册 Sa-Token 拦截器打开注解鉴权功能 + // 注册 Sa-Token 拦截器打开注解鉴权功能 registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); } @@ -40,7 +39,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { // 认证函数: 每次请求执行 .setAuth(obj -> { - // System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath()); + // SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue()); }) @@ -50,7 +49,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { return SaResult.error(e.getMessage()); }) - // 前置函数:在每次认证函数之前执行 + // 前置函数:在每次认证函数之前执行 (BeforeAuth不受 includeList 与 excludeList 的限制,所有请求都会进入) .setBeforeAuth(r -> { // ---------- 设置一些安全响应头 ---------- SaHolder.getResponse() diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java index d0aa2580..308bcaa8 100644 --- a/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/java/com/pj/test/TestController.java @@ -17,7 +17,7 @@ public class TestController { // 测试 浏览器访问: http://localhost:8081/test/test @RequestMapping("test") public SaResult test() { - System.out.println("------------进来了"); + System.out.println("------------进来了"); return SaResult.ok(); } diff --git a/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml index 3a41ae12..55685ecb 100644 --- a/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-springboot-redis/src/main/resources/application.yml @@ -18,8 +18,8 @@ sa-token: token-style: uuid # 是否输出操作日志 is-log: true - -spring: + +spring: # redis配置 redis: # Redis数据库索引(默认为0) diff --git a/sa-token-demo/sa-token-demo-springboot-redisson/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-springboot-redisson/src/main/java/com/pj/satoken/SaTokenConfigure.java index a4a4995e..9de045f8 100644 --- a/sa-token-demo/sa-token-demo-springboot-redisson/src/main/java/com/pj/satoken/SaTokenConfigure.java +++ b/sa-token-demo/sa-token-demo-springboot-redisson/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -50,7 +50,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { return SaResult.error(e.getMessage()); }) - // 前置函数:在每次认证函数之前执行 + // 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入) .setBeforeAuth(r -> { // ---------- 设置一些安全响应头 ---------- SaHolder.getResponse() diff --git a/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenConfigure.java index 30149e15..9d628520 100644 --- a/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenConfigure.java +++ b/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -41,7 +41,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { // 认证函数: 每次请求执行 .setAuth(obj -> { - // System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath()); + // SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue()); }) @@ -51,7 +51,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { return AjaxJson.getError(e.getMessage()); }) - // 前置函数:在每次认证函数之前执行 + // 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入) .setBeforeAuth(r -> { // ---------- 设置一些安全响应头 ---------- SaHolder.getResponse() diff --git a/sa-token-demo/sa-token-demo-springboot3-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-springboot3-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java index 07736e4e..c4cacc6e 100644 --- a/sa-token-demo/sa-token-demo-springboot3-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java +++ b/sa-token-demo/sa-token-demo-springboot3-redis/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -51,7 +51,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { return SaResult.error(e.getMessage()); }) - // 前置函数:在每次认证函数之前执行 + // 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入) .setBeforeAuth(r -> { // ---------- 设置一些安全响应头 ---------- SaHolder.getResponse() diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java index 9f6ecfce..d399e905 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java @@ -1,17 +1,17 @@ package com.pj.sso; +import cn.dev33.satoken.config.SaSsoConfig; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.sign.SaSignUtil; +import cn.dev33.satoken.sso.SaSsoProcessor; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; +import com.dtflys.forest.Forest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; -import com.dtflys.forest.Forest; - -import cn.dev33.satoken.config.SaSsoConfig; -import cn.dev33.satoken.sso.SaSsoProcessor; -import cn.dev33.satoken.stp.StpUtil; -import cn.dev33.satoken.util.SaResult; - /** * Sa-Token-SSO Server端 Controller * @author kong @@ -63,5 +63,23 @@ public class SsoServerController { } }); } - + + // 示例:获取数据接口(用于在模式三下,为 client 端开放拉取数据的接口) + @RequestMapping("/sso/getData") + public Object getData(String apiType, String loginId) { + System.out.println("---------------- 获取数据 ----------------"); + System.out.println("apiType=" + apiType); + System.out.println("loginId=" + loginId); + + // 校验签名:只有拥有正确秘钥发起的请求才能通过校验 + SaSignUtil.checkRequest(SaHolder.getRequest()); + + // 自定义返回结果(模拟) + return SaResult.ok() + .set("id", loginId) + .set("name", "LinXiaoYu") + .set("sex", "女") + .set("age", 18); + } + } diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/resources/application.yml index be264665..c7937759 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/resources/application.yml @@ -15,14 +15,14 @@ sa-token: ticket-timeout: 300 # 所有允许的授权回调地址 allow-url: "*" - # 是否打开单点注销功能 - is-slo: true - - # ------- SSO-模式三相关配置 (下面的配置在SSO模式三并且 is-slo=true 时打开) + + # ------- SSO-模式三相关配置 (下面的配置在使用SSO模式三时打开) # 是否打开模式三 - isHttp: true - # 接口调用秘钥(用于SSO模式三的单点注销功能) - secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + is-http: true + sign: + # API 接口调用秘钥 + secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + # ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明) spring: diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java index 111340fb..adbcbeef 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java @@ -1,17 +1,18 @@ package com.pj.sso; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.dtflys.forest.Forest; - import cn.dev33.satoken.config.SaSsoConfig; import cn.dev33.satoken.sso.SaSsoProcessor; import cn.dev33.satoken.sso.SaSsoUtil; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; +import com.dtflys.forest.Forest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; /** * Sa-Token-SSO Client端 Controller @@ -52,11 +53,17 @@ public class SsoClientController { } // 查询我的账号信息 - @RequestMapping("/sso/myinfo") - public Object myinfo() { - Object userinfo = SaSsoUtil.getUserinfo(StpUtil.getLoginId()); - System.out.println("--------info:" + userinfo); - return userinfo; + @RequestMapping("/sso/myInfo") + public Object myInfo() { + // 组织请求参数 + Map map = new HashMap<>(); + map.put("apiType", "userinfo"); + map.put("loginId", StpUtil.getLoginId()); + + // 发起请求 + Object resData = SaSsoUtil.getData(map); + System.out.println("sso-server 返回的信息:" + resData); + return resData; } // 全局异常拦截 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml index 5c2433d4..a61bc655 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/src/main/resources/application.yml @@ -8,19 +8,18 @@ sa-token: sso: # SSO-Server端 统一认证地址 auth-url: http://sa-sso-server.com:9000/sso/auth - # 使用Http请求校验ticket + # 使用 Http 请求校验ticket (模式三) is-http: true # SSO-Server端 ticket校验地址 check-ticket-url: http://sa-sso-server.com:9000/sso/checkTicket - # 打开单点注销功能 - is-slo: true # 单点注销地址 slo-url: http://sa-sso-server.com:9000/sso/signout - # 接口调用秘钥 - secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor - # SSO-Server端 查询userinfo地址 - userinfo-url: http://sa-sso-server.com:9000/sso/userinfo - + # 查询数据地址 + get-data-url: http://sa-sso-server.com:9000/sso/getData + sign: + # API 接口调用秘钥 + secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + spring: # 配置 Redis 连接 (此处与SSO-Server端连接不同的Redis) redis: diff --git a/sa-token-doc/more/common-questions.md b/sa-token-doc/more/common-questions.md index d068ba6b..6eb729fb 100644 --- a/sa-token-doc/more/common-questions.md +++ b/sa-token-doc/more/common-questions.md @@ -428,6 +428,12 @@ Caused by: java.lang.ClassNotFoundException: cn.dev33.satoken.same.SaSameTemplat - (2) 在自定义StpUtil类加上类似 @Component 的注解让容器启动时扫描到自动初始化 +### Q:使用拦截器鉴权,访问一个不存在的 path 时,springboot 会自动在控制台打印一下异常。 +可尝试添加以下配置解决: +``` properties +spring.resources.add-mappings=false +spring.mvc.throw-exception-if-no-handler-found=true +``` @@ -470,6 +476,30 @@ public class GlobalExceptionHandler { } ``` +### Q:在 SaInterceptor 中,注解鉴权总是先于路由拦截鉴权执行,能调整一下顺序吗? +框架没有提供直接的 API,但你有以下两种方式可以做到这一点: +- 方式1:将 SaInterceptor 里的代码复制出来一份,按照你的需求改一下,然后使用你这个自定义的拦截器,不再使用官方的。 +- 方式2:注册两次 SaInterceptor 拦截器,例如: + +``` java +@Configuration +public class SaTokenConfigure implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 路由拦截鉴权 + registry.addInterceptor(new SaInterceptor(r -> { + // 路由拦截鉴权的代码 ... + }).isAnnotation(false)).addPathPatterns("/**"); + + // 打开注解鉴权 + registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**"); + } +} +``` +如上,第一个完成路由拦截鉴权功能,第二个完成注解鉴权功能。 + + ### Q:我的项目权限模型不是RBAC模型,很复杂,可以集成吗? 无论什么模型,只要能把一个用户具有的所有权限塞到一个List里返回给框架,就能集成 diff --git a/sa-token-doc/plugin/jwt-extend.md b/sa-token-doc/plugin/jwt-extend.md index d5b17e50..7aed51f9 100644 --- a/sa-token-doc/plugin/jwt-extend.md +++ b/sa-token-doc/plugin/jwt-extend.md @@ -234,6 +234,12 @@ public void setSaJwtTemplate() { 技术上来讲无法将其踢下线,所以此时顶人下线和踢人下线等 API 都属于不可用状态,所以此时 `is-concurrent` 配置项必须配置为 `true`。 +##### 3、使用 jwt-mixin 模式后,max-try-times 恒等于 -1。 + +为防止框架错误判断 token 唯一性,当使用 jwt-mixin 模式后,`max-try-times` 恒等于 -1。 + + + diff --git a/sa-token-doc/sso/sso-questions.md b/sa-token-doc/sso/sso-questions.md index 1d0b8843..30bd127c 100644 --- a/sa-token-doc/sso/sso-questions.md +++ b/sa-token-doc/sso/sso-questions.md @@ -57,6 +57,9 @@ public class SaSsoServerApplication { 解决方案:在 sso-client 也新建上这个类,而且包名需要与 sso-server 端的一致(直接从 sso-server 把实体类复制过来就好了) +### 模式三配置一堆 xxx-url ,有办法简化一下吗? +可以使用 `sa-token.sso.server-url` 配置项来简化,参考:[配置项详解:serverurl](/use/config?id=配置项详解:serverurl) + ### 问:SSO模式二或模式三,第一个 client 登录成功之后再访问其它两个 client 不会自动登录,需要点一下登录按钮才会登录上? 答:这是正常现象,系统 1 登录成功之后,系统 2 与系统 3 需要点击登录按钮,才会登录成功。 @@ -184,6 +187,13 @@ public class SsoUserServerController { public StpLogic getStpLogic() { return StpUserUtil.stpLogic; } + // 使用自定义的签名秘钥 + SaSignConfig signConfig = new SaSignConfig().setSecretKey("xxxx-新的秘钥-xxxx"); + SaSignTemplate userSignTemplate = new SaSignTemplate().setSignConfig(signConfig); + @Override + public SaSignTemplate getSignTemplate() { + return userSignTemplate; + } }; // 让这个SSO请求处理器,使用的路由前缀是 /sso-user,而不是原先的 /sso ssoUserTemplate.apiName.replacePrefix("/sso-user"); diff --git a/sa-token-doc/sso/sso-server.md b/sa-token-doc/sso/sso-server.md index 6b1288d1..06fd6a87 100644 --- a/sa-token-doc/sso/sso-server.md +++ b/sa-token-doc/sso/sso-server.md @@ -178,14 +178,13 @@ sa-token: ticket-timeout: 300 # 所有允许的授权回调地址 allow-url: "*" - # 是否打开单点注销功能 - is-slo: true - # ------- SSO-模式三相关配置 (下面的配置在SSO模式三并且 is-slo=true 时打开) + # ------- SSO-模式三相关配置 (下面的配置在使用SSO模式三时打开) # 是否打开模式三 - isHttp: true - # 接口调用秘钥(用于SSO模式三的单点注销功能) - secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + is-http: true + sign: + # API 接口调用秘钥 + secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor # ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明) spring: @@ -219,14 +218,12 @@ server.port=9000 sa-token.sso.ticket-timeout=300 # 所有允许的授权回调地址 sa-token.sso.allow-url=* -# 是否打开单点注销功能 -sa-token.sso.is-slo=true -# ------- SSO-模式三相关配置 (下面的配置在SSO模式三并且 is-slo=true 时打开) +# ------- SSO-模式三相关配置 (下面的配置在使用SSO模式三时打开) # 是否打开模式三 -sa-token.sso.isHttp=true -# 接口调用秘钥(用于SSO模式三的单点注销功能) -sa-token.sso.secretkey=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor +sa-token.sso.is-http=true +# API 接口调用秘钥 +sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor # ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明) diff --git a/sa-token-doc/sso/sso-type2.md b/sa-token-doc/sso/sso-type2.md index a38f3153..901cc9e1 100644 --- a/sa-token-doc/sso/sso-type2.md +++ b/sa-token-doc/sso/sso-type2.md @@ -173,8 +173,6 @@ sa-token: sso: # SSO-Server端 统一认证地址 auth-url: http://sa-sso-server.com:9000/sso/auth - # 是否打开单点注销接口 - is-slo: true # 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis) alone-redis: @@ -197,8 +195,6 @@ server.port=9001 ######### Sa-Token 配置 ######### # SSO-Server端 统一认证地址 sa-token.sso.auth-url=http://sa-sso-server.com:9000/sso/auth -# 是否打开单点注销接口 -sa-token.sso.is-slo=true # 配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis) # Redis数据库索引 diff --git a/sa-token-doc/sso/sso-type3.md b/sa-token-doc/sso/sso-type3.md index 336f7265..194d6a4d 100644 --- a/sa-token-doc/sso/sso-type3.md +++ b/sa-token-doc/sso/sso-type3.md @@ -98,109 +98,160 @@ forest.log-enabled: false > 注:如果已测试运行模式二,可先将Redis中的数据清空,以防旧数据对测试造成干扰 -### 3、获取 Userinfo -除了账号id,我们可能还需要将用户的昵称、头像等信息从 Server端 带到 Client端,即:用户资料的同步。 +### 3、获取 UserInfo +除了账号id,我们可能还需要将用户的昵称、头像等信息从 Server端 带到 Client端,即:用户资料的拉取。 -在模式二中我们只需要将需要同步的资料放到 SaSession 即可,但是在模式三中两端不再连接同一个Redis,这时候我们需要通过http接口来同步信息: +在模式二中我们只需要将需要同步的资料放到 SaSession 即可,但是在模式三中两端不再连接同一个 Redis,这时候我们需要通过 http 接口来同步信息。 + +在旧版本`(<= v1.34.0)` 框架提供的方案是配置 getUserinfo 接口地址,从 client 调用拉取数据,该方案有以下缺点: +- 每次调用只能传递固定 loginId 一个参数,不方便。 +- 只能拉取 userinfo 数据,不通用。 +- 如果还需要拉取其它业务数据,需要再自定义一个接口,比较麻烦。 + +为此,我们设计了更通用、灵活的 getData 接口,解决上述三个难题。 + +#### 3.1、首先在 Server 端开放一个查询数据的接口 -#### 3.1、在 Server 端自定义接口,查询用户资料 ``` java -// 自定义接口:获取userinfo -@RequestMapping("/sso/userinfo") -public Object userinfo(String loginId) { - System.out.println("---------------- 获取userinfo --------"); - - // 校验签名,防止敏感信息外泄 - SaSsoUtil.checkSign(SaHolder.getRequest()); +// 示例:获取数据接口(用于在模式三下,为 client 端开放拉取数据的接口) +@RequestMapping("/sso/getData") +public Object getData(String apiType, String loginId) { + System.out.println("---------------- 获取数据 ----------------"); + System.out.println("apiType=" + apiType); + System.out.println("loginId=" + loginId); + + // 校验签名:只有拥有正确秘钥发起的请求才能通过校验 + SaSignUtil.checkRequest(SaHolder.getRequest()); // 自定义返回结果(模拟) return SaResult.ok() .set("id", loginId) - .set("name", "linxiaoyu") + .set("name", "LinXiaoYu") .set("sex", "女") .set("age", 18); } ``` -#### 3.2、在 Client 端调用此接口查询 userinfo +#### 3.2、在 Client 端调用此接口查询数据 + 首先在 application.yml 中配置接口地址: ``` yaml sa-token: sso: - # SSO-Server端 查询userinfo地址 - userinfo-url: http://sa-sso-server.com:9000/sso/userinfo + # sso-server 端拉取数据地址 + get-data-url: http://sa-sso-server.com:9000/sso/getData ``` ``` properties -# SSO-Server端 查询userinfo地址 -sa-token.sso.userinfo-url=http://sa-sso-server.com:9000/sso/userinfo +# sso-server 端拉取数据地址 +sa-token.sso.get-data-url=http://sa-sso-server.com:9000/sso/getData ``` - - -然后在`SsoClientController`中新增接口 +然后在 `SsoClientController` 中新增接口 ``` java // 查询我的账号信息 -@RequestMapping("/sso/myinfo") -public Object myinfo() { - Object userinfo = SaSsoUtil.getUserinfo(StpUtil.getLoginId()); - System.out.println("--------info:" + userinfo); - return userinfo; +@RequestMapping("/sso/myInfo") +public Object myInfo() { + // 组织请求参数 + Map map = new HashMap<>(); + map.put("apiType", "userinfo"); + map.put("loginId", StpUtil.getLoginId()); + + // 发起请求 + Object resData = SaSsoUtil.getData(map); + System.out.println("sso-server 返回的信息:" + resData); + return resData; } ``` #### 3.3、访问测试 -访问测试:[http://sa-sso-client1.com:9001/sso/myinfo](http://sa-sso-client1.com:9001/sso/myinfo) +访问测试:[http://sa-sso-client1.com:9001/sso/myInfo](http://sa-sso-client1.com:9001/sso/myInfo) ### 4、自定义接口通信 -群里有小伙伴提问:`SaSsoUtil.getUserinfo` 提供的参数太少,只有一个 loginId,无法满足业务需求怎么办? +上述示例展示在 client 端向 server 拉取 userinfo 数据的步骤,如果你还需要拉取其它业务的数据,稍加改造示例便可以实现。 -答:SaSsoUtil.getUserinfo只是为了避免你在项目中硬编码认证中心 url 而提供的简易封装,如果这个API无法满足你的业务需求, -你完全可以在 Server 端自定义一些接口然后从 Client 端使用 http 工具调用即可。 +#### 4.1、方式一,使用 apiType 参数来区分业务 -以下是一个简单的示例: +我们可以约定好,使用 apiType 来区分不同的业务,例如: +- 当 `apiType=userinfo` 时:代表拉取用户资料。 +- 当 `apiType=followList` 时:代表拉取用户的关注列表。 +- 当 `apiType=fansList` 时:代表拉取用户的粉丝列表。 -#### 4.1、先在 sso-server 端自定义一个接口 -``` java -// 获取指定用户的关注列表 -@RequestMapping("/sso/getFollowList") -public Object ssoRequest(Long loginId) { +此时,我们便可以通过在 client 端传入不同的 apiType 参数,来区分不同的业务。 - // 校验签名,签名不通过直接抛出异常 - SaSsoUtil.checkSign(SaHolder.getRequest()); - - // 查询数据 (此处仅做模拟) - List list = Arrays.asList(10041, 10042, 10043, 10044); - - // 返回 - return list; -} -``` - - -#### 4.2、然后在 sso-client 端调用这个接口 ``` java // 查询我的账号信息 @RequestMapping("/sso/myFollowList") public Object myFollowList() { - // 组织url,加上签名参数 - String url = SaSsoUtil.addSignParams("http://sa-sso-server.com:9000/sso/getFollowList", StpUtil.getLoginId()); - - // 调用,并返回 SaResult 结果 - SaResult res = SaSsoUtil.request(url); - - // 返回给前端 - return res; + // 组织请求参数 + Map map = new HashMap<>(); + map.put("apiType", "followList"); // 关键代码,代表本次我要拉取关注列表 + map.put("loginId", StpUtil.getLoginId()); + + // 发起请求 + Object resData = SaSsoUtil.getData(map); + System.out.println("sso-server 返回的信息:" + resData); + return resData; } ``` +然后在 server 端我们通过不同的 apiType 值,返回不同的信息即可。 + + +#### 4.2、方式二:直接在调用接口时传入一个自定义 path + +我们可以 client 端,调用 `SaSsoUtil.getData` 方法时,传入一个自定义 path,例如: + +``` java +// 查询我的账号信息 +@RequestMapping("/sso/myFansList") +public Object myFansList() { + // 组织请求参数 + Map map = new HashMap<>(); + // map.put("apiType", "userinfo"); // 此时已经不需要 apiType 参数了 + map.put("loginId", StpUtil.getLoginId()); + + // 发起请求 (传入自定义的 path 地址) + Object resData = SaSsoUtil.getData("/sso/getFansList", map); + System.out.println("sso-server 返回的信息:" + resData); + return resData; +} +``` + +同时,我们需要在 server 端开放这个自定义的 `/sso/getFansList` 接口: + +``` java +// 获取指定用户的粉丝列表 +@RequestMapping("/sso/getFansList") +public Object getFansList(Long loginId) { + System.out.println("---------------- 获取 loginId=" + loginId + " 的粉丝列表 ----------------"); + + // 校验签名:只有拥有正确秘钥发起的请求才能通过校验 + SaSignUtil.checkRequest(SaHolder.getRequest()); + + // 查询数据 (此处仅做模拟) + List list = Arrays.asList(10041, 10042, 10043, 10044); + + // 返回 + return list; +} +``` + +**注意:使用此方案时,需要在 client 端配置 `sa-token.sso.server-url` 地址,例如:** +``` yaml +sa-token: + sso: + # sso-server 端主机地址 + server-url: http://sa-sso-server.com:9000 +``` + #### 4.3、访问测试 -访问测试:[http://sa-sso-client1.com:9001/sso/myFollowList](http://sa-sso-client1.com:9001/sso/myFollowList) +访问测试:[http://sa-sso-client1.com:9001/sso/myFansList](http://sa-sso-client1.com:9001/sso/myFansList) @@ -233,12 +284,11 @@ public Object myFollowList() { ``` yaml sa-token: sso: - # 打开单点注销功能 - is-slo: true # 单点注销地址 slo-url: http://sa-sso-server.com:9000/sso/signout - # 接口调用秘钥 - secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + sign: + # API 接口调用秘钥 + secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor ``` ``` properties @@ -247,7 +297,7 @@ sa-token.sso.is-slo=true # 单点注销地址 sa-token.sso.slo-url=http://sa-sso-server.com:9000/sso/signout # 接口调用秘钥 -sa-token.sso.secretkey=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor +sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor ``` diff --git a/sa-token-doc/up/global-filter.md b/sa-token-doc/up/global-filter.md index d305f2bf..fe2f5a32 100644 --- a/sa-token-doc/up/global-filter.md +++ b/sa-token-doc/up/global-filter.md @@ -53,7 +53,7 @@ public class SaTokenConfigure { return SaResult.error(e.getMessage()); }) - // 前置函数:在每次认证函数之前执行 + // 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入) .setBeforeAuth(r -> { // ---------- 设置一些安全响应头 ---------- SaHolder.getResponse() diff --git a/sa-token-doc/use/config.md b/sa-token-doc/use/config.md index aa5bd486..02308f84 100644 --- a/sa-token-doc/use/config.md +++ b/sa-token-doc/use/config.md @@ -110,7 +110,7 @@ public class SaTokenConfigure { --- ### 所有可配置项 -你不必立刻掌握整个表格,只需要在用到某个功能时再详细查阅它即可 +**你不必立刻掌握整个表格,只需要在用到某个功能时再详细查阅它即可** | 参数名称 | 类型 | 默认值 | 说明 | | :-------- | :-------- | :-------- | :-------- | @@ -120,6 +120,7 @@ public class SaTokenConfigure { | isConcurrent | Boolean | true | 是否允许同一账号并发登录 (为 true 时允许一起登录,为 false 时新登录挤掉旧登录) | | isShare | Boolean | true | 在多人登录同一账号时,是否共用一个token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token) | | maxLoginCount | int | 12 | 同一账号最大登录数量,-1代表不限 (只有在 `isConcurrent=true`, `isShare=false` 时此配置才有效),[详解](/use/config?id=配置项详解:maxlogincount) | +| maxTryTimes | int | 12 | 在每次创建 Token 时的最高循环次数,用于保证 Token 唯一性(-1=不循环重试,直接使用) | | isReadBody | Boolean | true | 是否尝试从 请求体 里读取 Token | | isReadHeader | Boolean | true | 是否尝试从 header 里读取 Token | | isReadCookie | Boolean | true | 是否尝试从 cookie 里读取 Token,此值为 false 后,`StpUtil.login(id)` 登录时也不会再往前端注入Cookie | @@ -148,6 +149,31 @@ Cookie相关配置: | httpOnly | Boolean | false | 是否禁止 js 操作 Cookie | | sameSite | String | Lax | 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制) | +Cookie 配置示例: + + + +``` yaml +# Sa-Token 配置 +sa-token: + # Cookie 相关配置 + cookie: + domain: stp.com + path: / + secure: false + httpOnly: true + sameSite: Lax +``` + +``` properties +# Cookie 相关配置 +sa-token.cookie.domain=stp.com +sa-token.cookie.path=/ +sa-token.cookie.secure=false +sa-token.cookie.httpOnly=true +sa-token.cookie.sameSite=Lax +``` + ### 单点登录相关配置 @@ -319,7 +345,7 @@ sa-token.oauth2.is-client=true #### 配置项详解:serverUrl -配置含义:配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、userinfoUrl、sloUrl 属性前面,用以简化各种 url 配置。 +配置含义:配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置。 在开发 SSO 模块时,我们需要在 sso-client 配置认证中心的各种地址,特别是在模式三下,一般代码会变成这样: @@ -332,8 +358,8 @@ sa-token: check-ticket-url: http://sa-sso-server.com:9000/sso/checkTicket # 单点注销地址 slo-url: http://sa-sso-server.com:9000/sso/signout - # SSO-Server端 查询userinfo地址 - userinfo-url: http://sa-sso-server.com:9000/sso/userinfo + # SSO-Server端 查询数据地址 + get-data-url: http://sa-sso-server.com:9000/sso/getData ``` 一堆 xxx-url 配置比较繁琐,且含有大量重复字符,现在我们可以将其简化为: diff --git a/sa-token-plugin/sa-token-context-dubbo/src/main/java/cn/dev33/satoken/context/dubbo/model/SaRequestForDubbo.java b/sa-token-plugin/sa-token-context-dubbo/src/main/java/cn/dev33/satoken/context/dubbo/model/SaRequestForDubbo.java index 3e506528..9ee00aea 100644 --- a/sa-token-plugin/sa-token-context-dubbo/src/main/java/cn/dev33/satoken/context/dubbo/model/SaRequestForDubbo.java +++ b/sa-token-plugin/sa-token-context-dubbo/src/main/java/cn/dev33/satoken/context/dubbo/model/SaRequestForDubbo.java @@ -4,6 +4,9 @@ import org.apache.dubbo.rpc.RpcContext; import cn.dev33.satoken.context.model.SaRequest; +import java.util.List; +import java.util.Map; + /** * Request for Dubbo * @@ -42,6 +45,24 @@ public class SaRequestForDubbo implements SaRequest { return null; } + /** + * 获取 [请求体] 里提交的所有参数名称 + * @return 参数名称列表 + */ + @Override + public List getParamNames(){ + return null; + } + + /** + * 获取 [请求体] 里提交的所有参数 + * @return 参数列表 + */ + @Override + public Map getParamMap(){ + return null; + } + /** * 在 [请求头] 里获取一个值 */ diff --git a/sa-token-plugin/sa-token-context-dubbo3/src/main/java/cn/dev33/satoken/context/dubbo3/model/SaRequestForDubbo3.java b/sa-token-plugin/sa-token-context-dubbo3/src/main/java/cn/dev33/satoken/context/dubbo3/model/SaRequestForDubbo3.java index 655cdd95..b2efb9fd 100644 --- a/sa-token-plugin/sa-token-context-dubbo3/src/main/java/cn/dev33/satoken/context/dubbo3/model/SaRequestForDubbo3.java +++ b/sa-token-plugin/sa-token-context-dubbo3/src/main/java/cn/dev33/satoken/context/dubbo3/model/SaRequestForDubbo3.java @@ -3,6 +3,9 @@ package cn.dev33.satoken.context.dubbo3.model; import cn.dev33.satoken.context.model.SaRequest; import org.apache.dubbo.rpc.RpcContext; +import java.util.List; +import java.util.Map; + /** * Request for Dubbo3 * @@ -41,6 +44,24 @@ public class SaRequestForDubbo3 implements SaRequest { return null; } + /** + * 获取 [请求体] 里提交的所有参数名称 + * @return 参数名称列表 + */ + @Override + public List getParamNames(){ + return null; + } + + /** + * 获取 [请求体] 里提交的所有参数 + * @return 参数列表 + */ + @Override + public Map getParamMap(){ + return null; + } + /** * 在 [请求头] 里获取一个值 */ diff --git a/sa-token-plugin/sa-token-context-grpc/src/main/java/cn/dev33/satoken/context/grpc/model/SaRequestForGrpc.java b/sa-token-plugin/sa-token-context-grpc/src/main/java/cn/dev33/satoken/context/grpc/model/SaRequestForGrpc.java index 269878bd..cbff558f 100644 --- a/sa-token-plugin/sa-token-context-grpc/src/main/java/cn/dev33/satoken/context/grpc/model/SaRequestForGrpc.java +++ b/sa-token-plugin/sa-token-context-grpc/src/main/java/cn/dev33/satoken/context/grpc/model/SaRequestForGrpc.java @@ -3,6 +3,9 @@ package cn.dev33.satoken.context.grpc.model; import cn.dev33.satoken.context.grpc.context.SaTokenGrpcContext; import cn.dev33.satoken.context.model.SaRequest; +import java.util.List; +import java.util.Map; + /** * Request for grpc * @@ -27,6 +30,24 @@ public class SaRequestForGrpc implements SaRequest { return null; } + /** + * 获取 [请求体] 里提交的所有参数名称 + * @return 参数名称列表 + */ + @Override + public List getParamNames(){ + return null; + } + + /** + * 获取 [请求体] 里提交的所有参数 + * @return 参数列表 + */ + @Override + public Map getParamMap(){ + return null; + } + /** * 在 [请求头] 里获取一个值 */ 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 index 21e7fe3f..5b2612bb 100644 --- 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 @@ -212,12 +212,21 @@ public class StpLogicJwtForMixin extends StpLogic { // ------------------- Bean对象代理 ------------------- /** - * 返回全局配置对象的isShare属性 + * 返回全局配置对象的 isShare 属性 * @return / */ @Override public boolean getConfigOfIsShare() { return false; } - + + /** + * 返回全局配置对象的 maxTryTimes 属性 + * @return / + */ + @Override + public int getConfigOfMaxTryTimes() { + return -1; + } + } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/config/SaSsoConfig.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/config/SaSsoConfig.java index 6ed799ad..8909fcd8 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/config/SaSsoConfig.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/config/SaSsoConfig.java @@ -43,11 +43,6 @@ public class SaSsoConfig implements Serializable { */ public Boolean isHttp = false; - /** - * 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验) - */ - public String secretkey; - // ----------------- Client端相关配置 @@ -61,31 +56,36 @@ public class SaSsoConfig implements Serializable { */ public String authUrl = "/sso/auth"; - /** - * 是否打开单点注销功能 - */ - // public Boolean isSlo = true; // 同Server端 + // /** + // * 是否打开单点注销功能 + // */ + // public Boolean isSlo = true; // 同Server端,不再重复声明 - /** - * 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo) - */ - // public Boolean isHttp = false; // 同Server端 + // /** + // * 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo) + // */ + // public Boolean isHttp = false; // 同Server端,不再重复声明 - /** - * 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验) - */ - // public String secretkey; // 同Server端 + // /** + // * 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验) + // */ + // public String secretkey; // 同Server端,不再重复声明 /** * 配置 Server 端的 ticket 校验地址 */ public String checkTicketUrl = "/sso/checkTicket"; + /** + * 配置 Server 端查询数据 getData 地址 + */ + public String getDataUrl = "/sso/getData"; + /** * 配置 Server 端查询 userinfo 地址 */ public String userinfoUrl = "/sso/userinfo"; - + /** * 配置 Server 端单点注销地址 */ @@ -97,21 +97,10 @@ public class SaSsoConfig implements Serializable { public String ssoLogoutCall; /** - * 配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、userinfoUrl、sloUrl 属性前面,用以简化各种 url 配置 + * 配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置 */ public String serverUrl; - // ----------------- 其它 - - - - /** - * 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距 - */ - public long timestampDisparity = 1000 * 60 * 10; - - - /** * @return Ticket有效期 (单位: 秒) @@ -177,22 +166,6 @@ public class SaSsoConfig implements Serializable { return this; } - /** - * @return 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验) - */ - public String getSecretkey() { - return secretkey; - } - - /** - * @param secretkey 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验) - * @return 对象自身 - */ - public SaSsoConfig setSecretkey(String secretkey) { - this.secretkey = secretkey; - return this; - } - /** * @return 当前 Client 名称标识,用于和 ticket 码的互相锁定 */ @@ -240,6 +213,22 @@ public class SaSsoConfig implements Serializable { return this; } + /** + * @return Server 端查询数据 getData 地址 + */ + public String getGetDataUrl() { + return getDataUrl; + } + + /** + * @param getDataUrl 配置 Server 端查询数据 getData 地址 + * @return 对象自身 + */ + public SaSsoConfig setGetDataUrl(String getDataUrl) { + this.getDataUrl = getDataUrl; + return this; + } + /** * @return 配置的 Server 端查询 userinfo 地址 */ @@ -289,14 +278,14 @@ public class SaSsoConfig implements Serializable { } /** - * @return 配置的 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、userinfoUrl、sloUrl 属性前面,用以简化各种 url 配置 + * @return 配置的 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置 */ public String getServerUrl() { return serverUrl; } /** - * @param serverUrl 配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、userinfoUrl、sloUrl 属性前面,用以简化各种 url 配置 + * @param serverUrl 配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置 * @return 对象自身 */ public SaSsoConfig setServerUrl(String serverUrl) { @@ -304,38 +293,21 @@ public class SaSsoConfig implements Serializable { return this; } - /** - * @return 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距 - */ - public long getTimestampDisparity() { - return timestampDisparity; - } - - /** - * @param timestampDisparity 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距 - * @return 对象自身 - */ - public SaSsoConfig setTimestampDisparity(long timestampDisparity) { - this.timestampDisparity = timestampDisparity; - return this; - } - @Override public String toString() { return "SaSsoConfig [" + "ticketTimeout=" + ticketTimeout + ", allowUrl=" + allowUrl + ", isSlo=" + isSlo - + ", isHttp=" + isHttp - + ", secretkey=" + secretkey + + ", isHttp=" + isHttp + ", client=" + client + ", authUrl=" + authUrl + ", checkTicketUrl=" + checkTicketUrl + + ", getDataUrl=" + getDataUrl + ", userinfoUrl=" + userinfoUrl + ", sloUrl=" + sloUrl + ", ssoLogoutCall=" + ssoLogoutCall - + ", serverUrl=" + serverUrl - + ", timestampDisparity=" + timestampDisparity + + ", serverUrl=" + serverUrl + "]"; } @@ -356,6 +328,13 @@ public class SaSsoConfig implements Serializable { return SaFoxUtil.spliceTwoUrl(getServerUrl(), getCheckTicketUrl()); } + /** + * @return 获取拼接url:Server 端查询数据 getData 地址 + */ + public String splicingGetDataUrl() { + return SaFoxUtil.spliceTwoUrl(getServerUrl(), getGetDataUrl()); + } + /** * @return 获取拼接url:Server 端查询 userinfo 地址 */ diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoProcessor.java index bcb1d227..b75702bd 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoProcessor.java @@ -193,7 +193,7 @@ public class SaSsoProcessor { String loginId = req.getParam(paramName.loginId); // step.1 校验签名 - ssoTemplate.checkSign(req); + ssoTemplate.getSignTemplate().checkRequest(req); // step.2 单点注销 ssoTemplate.ssoLogout(loginId); @@ -374,7 +374,7 @@ public class SaSsoProcessor { String loginId = req.getParamNotNull(paramName.loginId); // 注销当前应用端会话 - ssoTemplate.checkSign(req); + ssoTemplate.getSignTemplate().checkRequest(req); stpLogic.logout(loginId); // 响应 diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoTemplate.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoTemplate.java index d287e5c4..4386090a 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoTemplate.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoTemplate.java @@ -1,16 +1,9 @@ package cn.dev33.satoken.sso; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - import cn.dev33.satoken.SaManager; import cn.dev33.satoken.config.SaSsoConfig; -import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.sign.SaSignTemplate; import cn.dev33.satoken.sso.error.SaSsoErrorCode; import cn.dev33.satoken.sso.exception.SaSsoException; import cn.dev33.satoken.sso.name.ApiName; @@ -21,6 +14,8 @@ import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; +import java.util.*; + /** * Sa-Token-SSO 单点登录模块 * @author kong @@ -73,8 +68,16 @@ public class SaSsoTemplate { public SaSsoConfig getSsoConfig() { return SaSsoManager.getConfig(); } - - + + /** + * 获取底层使用的 API 签名对象 + * @return / + */ + public SaSignTemplate getSignTemplate() { + return SaManager.getSaSignTemplate(); + } + + // ---------------------- Ticket 操作 ---------------------- /** @@ -153,7 +156,7 @@ public class SaSsoTemplate { } String loginId = SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket)); // 如果是 "a,b" 的格式,则只取最前面的一项 - if(loginId != null && loginId.indexOf(",") > -1) { + if(loginId != null && loginId.contains(",")) { String[] arr = loginId.split(","); loginId = arr[0]; } @@ -206,7 +209,7 @@ public class SaSsoTemplate { // 如果是 "a,b" 的格式,则解析出对应的 Client String ticketClient = null; - if(loginId.indexOf(",") > -1) { + if(loginId.contains(",")) { String[] arr = loginId.split(","); loginId = arr[0]; ticketClient = arr[1]; @@ -252,7 +255,7 @@ public class SaSsoTemplate { public void checkRedirectUrl(String url) { // 1、是否是一个有效的url - if(SaFoxUtil.isUrl(url) == false) { + if( ! SaFoxUtil.isUrl(url) ) { throw new SaSsoException("无效redirect:" + url).setCode(SaSsoErrorCode.CODE_30001); } @@ -264,12 +267,11 @@ public class SaSsoTemplate { // 3、是否在[允许地址列表]之中 List authUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(",")); - if(SaStrategy.me.hasElement.apply(authUrlList, url) == false) { + if( ! SaStrategy.me.hasElement.apply(authUrlList, url) ) { throw new SaSsoException("非法redirect:" + url).setCode(SaSsoErrorCode.CODE_30002); } - // 校验通过 √ - return; + // 校验通过 √ } @@ -285,7 +287,7 @@ public class SaSsoTemplate { return; } SaSession session = getStpLogic().getSessionByLoginId(loginId); - Set urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, ()-> new HashSet()); + Set urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, HashSet::new); urlSet.add(sloCallbackUrl); session.set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet); } @@ -304,9 +306,9 @@ public class SaSsoTemplate { // step.1 遍历通知 Client 端注销会话 SaSsoConfig cfg = SaSsoManager.getConfig(); - Set urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, () -> new HashSet()); + Set urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, HashSet::new); for (String url : urlSet) { - url = addSignParams(url, loginId); + url = joinLoginIdAndSign(url, loginId); cfg.getSendHttp().apply(url); } @@ -315,12 +317,23 @@ public class SaSsoTemplate { } /** - * 获取:账号资料 - * @param loginId 账号id - * @return 账号资料 + * 根据配置的 getData 地址,查询数据 + * @param paramMap 查询参数 + * @return 查询结果 */ - public Object getUserinfo(Object loginId) { - String url = buildUserinfoUrl(loginId); + public Object getData(Map paramMap) { + String getDataUrl = SaSsoManager.getConfig().splicingGetDataUrl(); + return getData(getDataUrl, paramMap); + } + + /** + * 根据自定义 path 地址,查询数据 (此方法需要配置 sa-token.sso.server-url 地址) + * @param path 自定义 path + * @param paramMap 查询参数 + * @return 查询结果 + */ + public Object getData(String path, Map paramMap) { + String url = buildCustomPathUrl(path, paramMap); return SaSsoManager.getConfig().getSendHttp().apply(url); } @@ -355,17 +368,16 @@ public class SaSsoTemplate { * 部分 Servlet 版本 request.getRequestURL() 返回的 url 带有 query 参数,形如:http://domain.com?id=1, * 如果不加判断会造成最终生成的 serverAuthUrl 带有双 back 参数 ,这个 if 判断正是为了解决此问题 */ - if(clientLoginUrl.indexOf(paramName.back + "=" + back) == -1) { + if( ! clientLoginUrl.contains(paramName.back + "=" + back) ) { clientLoginUrl = SaFoxUtil.joinParam(clientLoginUrl, paramName.back, back); } - String serverAuthUrl = SaFoxUtil.joinParam(serverUrl, paramName.redirect, clientLoginUrl); - + // 返回 - return serverAuthUrl; + return SaFoxUtil.joinParam(serverUrl, paramName.redirect, clientLoginUrl); } /** - * 构建URL:Server端向Client下放ticke的地址 + * 构建URL:Server端向Client下放ticket的地址 * @param loginId 账号id * @param client 客户端标识 * @param redirect Client端提供的重定向地址 @@ -412,16 +424,6 @@ public class SaSsoTemplate { return url; } - /** - * 构建URL:Server端 账号资料查询地址 - * @param loginId 账号id - * @return Server端 账号资料查询地址 - */ - public String buildUserinfoUrl(Object loginId) { - String userinfoUrl = SaSsoManager.getConfig().splicingUserinfoUrl(); - return addSignParams(userinfoUrl, loginId); - } - /** * 构建URL:校验ticket的URL *

在模式三下,Client端拿到Ticket后根据此地址向Server端发送请求,获取账号id @@ -458,32 +460,39 @@ public class SaSsoTemplate { */ public String buildSloUrl(Object loginId) { String url = SaSsoManager.getConfig().splicingSloUrl(); - return addSignParams(url, loginId); + return joinLoginIdAndSign(url, loginId); } - - // ------------------- 返回相应key ------------------- - - /** - * 拼接key:Ticket 查 账号Id - * @param ticket ticket值 - * @return key + /** + * 构建URL:Server端 getData 地址,带签名等参数 + * @param paramMap 查询参数 + * @return / */ - public String splicingTicketSaveKey(String ticket) { - return SaManager.getConfig().getTokenName() + ":ticket:" + ticket; + public String buildGetDataUrl(Map paramMap) { + String getDataUrl = SaSsoManager.getConfig().getGetDataUrl(); + return buildCustomPathUrl(getDataUrl, paramMap); } - /** - * 拼接key:账号Id 反查 Ticket - * @param id 账号id - * @return key + /** + * 构建URL:Server 端自定义 path 地址,带签名等参数 (此方法需要配置 sa-token.sso.server-url 地址) + * @param paramMap 请求参数 + * @return / */ - public String splicingTicketIndexKey(Object id) { - return SaManager.getConfig().getTokenName() + ":id-ticket:" + id; + public String buildCustomPathUrl(String path, Map paramMap) { + // 如果path不是以 http 开头,那么就拼接上 serverUrl + String url = path; + if( ! url.startsWith("http") ) { + String serverUrl = SaSsoManager.getConfig().getServerUrl(); + SaSsoException.throwByNull(serverUrl, "请先配置 sa-token.sso.server-url 地址", SaSsoErrorCode.CODE_30012); + url = SaFoxUtil.spliceTwoUrl(serverUrl, path); + } + + // 添加签名等参数,并序列化 + return joinParamMapAndSign(url, paramMap); } - // ------------------- 请求相关 ------------------- + // ------------------- 发起请求 ------------------- /** * 发出请求,并返回 SaResult 结果 @@ -497,98 +506,79 @@ public class SaSsoTemplate { } /** - * 获取:接口调用秘钥 - * @return see note + * 给 paramMap 追加 sign 等参数,并序列化为kv字符串,拼接到url后面 + * @param url 请求地址 + * @param paramMap 请求原始参数列表 + * @return 加工后的url */ - public String getSecretkey() { - // 默认从配置文件中返回 - String secretkey = SaSsoManager.getConfig().getSecretkey(); - if(SaFoxUtil.isEmpty(secretkey)) { - throw new SaSsoException("请配置 secretkey 参数").setCode(SaSsoErrorCode.CODE_30009); - } - return secretkey; + public String joinParamMapAndSign(String url, Map paramMap) { + // 在参数列表中追加:时间戳、随机字符串、参数签名 + SaManager.getSaSignTemplate().addSignParams(paramMap); + + // 将参数列表序列化为kv字符串 + String signParams = SaManager.getSaSignTemplate().joinParams(paramMap); + + // 将kv字符串拼接到url后面 + return SaFoxUtil.joinParam(url, signParams); } /** - * 校验secretkey秘钥是否有效 (API已过期,请更改为更安全的 sign 式校验) - * @param secretkey 秘钥 + * 给 url 拼接 loginId 参数,并拼接 sign 等参数 + * @param url 链接 + * @param loginId 账号id + * @return 加工后的url + */ + public String joinLoginIdAndSign(String url, Object loginId) { + Map paramMap = new LinkedHashMap<>(); + paramMap.put(paramName.loginId, loginId); + return joinParamMapAndSign(url, paramMap); + } + + + // ------------------- 返回相应key ------------------- + + /** + * 拼接key:Ticket 查 账号Id + * @param ticket ticket值 + * @return key + */ + public String splicingTicketSaveKey(String ticket) { + return SaManager.getConfig().getTokenName() + ":ticket:" + ticket; + } + + /** + * 拼接key:账号Id 反查 Ticket + * @param id 账号id + * @return key + */ + public String splicingTicketIndexKey(Object id) { + return SaManager.getConfig().getTokenName() + ":id-ticket:" + id; + } + + + // -------- 以下方法已废弃,仅为兼容旧版本而保留 -------- + + /** + * 构建URL:Server端 账号资料查询地址 + * @param loginId 账号id + * @return Server端 账号资料查询地址 */ @Deprecated - public void checkSecretkey(String secretkey) { - if(SaFoxUtil.isEmpty(secretkey) || secretkey.equals(getSecretkey()) == false) { - throw new SaSsoException("无效秘钥:" + secretkey).setCode(SaSsoErrorCode.CODE_30003); - } + public String buildUserinfoUrl(Object loginId) { + String userinfoUrl = SaSsoManager.getConfig().splicingUserinfoUrl(); + return joinLoginIdAndSign(userinfoUrl, loginId); } - + /** - * 根据参数计算签名 + * 获取:账号资料 * @param loginId 账号id - * @param timestamp 当前时间戳,13位 - * @param nonce 随机字符串 - * @param secretkey 账号id - * @return 签名 + * @return 账号资料 */ - public String getSign(Object loginId, String timestamp, String nonce, String secretkey) { - Map map = new TreeMap<>(); - map.put(paramName.loginId, loginId); - map.put(paramName.timestamp, timestamp); - map.put(paramName.nonce, nonce); - return SaManager.getSaSignTemplate().createSign(map, secretkey); + @Deprecated + public Object getUserinfo(Object loginId) { + String url = buildUserinfoUrl(loginId); + return request(url); } - /** - * 给 url 追加 sign 等参数 - * @param url 连接 - * @param loginId 账号id - * @return 加工后的url - */ - public String addSignParams(String url, Object loginId) { - - // 时间戳、随机字符串、参数签名 - String timestamp = String.valueOf(System.currentTimeMillis()); - String nonce = SaFoxUtil.getRandomString(20); - String sign = getSign(loginId, timestamp, nonce, getSecretkey()); - - // 追加到url - url = SaFoxUtil.joinParam(url, paramName.loginId, loginId); - url = SaFoxUtil.joinParam(url, paramName.timestamp, timestamp); - url = SaFoxUtil.joinParam(url, paramName.nonce, nonce); - url = SaFoxUtil.joinParam(url, paramName.sign, sign); - return url; - } - /** - * 校验签名 - * @param req request - */ - public void checkSign(SaRequest req) { - - // 参数签名、账号id、时间戳、随机字符串 - String sign = req.getParamNotNull(paramName.sign); - String loginId = req.getParamNotNull(paramName.loginId); - String timestamp = req.getParamNotNull(paramName.timestamp); - String nonce = req.getParamNotNull(paramName.nonce); - - // 校验时间戳 - checkTimestamp(Long.valueOf(timestamp)); - - // 校验签名 - String calcSign = getSign(loginId, timestamp, nonce, getSecretkey()); - if(calcSign.equals(sign) == false) { - throw new SaSsoException("签名无效:" + calcSign).setCode(SaSsoErrorCode.CODE_30008); - } - } - - /** - * 校验时间戳与当前时间的差距是否超出限制 - * @param timestamp 时间戳 - */ - public void checkTimestamp(long timestamp) { - long disparity = Math.abs(System.currentTimeMillis() - timestamp); - long allowDisparity = SaSsoManager.getConfig().getTimestampDisparity(); - if(allowDisparity != -1 && disparity > allowDisparity) { - throw new SaSsoException("timestamp 超出允许的范围").setCode(SaSsoErrorCode.CODE_30007); - } - } - } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoUtil.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoUtil.java index 60f875b9..a4fc2880 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoUtil.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoUtil.java @@ -1,8 +1,9 @@ package cn.dev33.satoken.sso; -import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.util.SaResult; +import java.util.Map; + /** * Sa-Token-SSO 单点登录模块 工具类 * @author kong @@ -139,12 +140,22 @@ public class SaSsoUtil { } /** - * 获取:账号资料 - * @param loginId 账号id - * @return 账号资料 + * 获取:查询数据 + * @param paramMap 查询参数 + * @return 查询结果 */ - public static Object getUserinfo(Object loginId) { - return ssoTemplate.getUserinfo(loginId); + public static Object getData(Map paramMap) { + return ssoTemplate.getData(paramMap); + } + + /** + * 根据自定义 path 查询数据 (此方法需要配置 sa-token.sso.server-url 地址) + * @param path 自定义 path + * @param paramMap 查询参数 + * @return 查询结果 + */ + public static Object getData(String path, Map paramMap) { + return ssoTemplate.getData(path, paramMap); } @@ -161,7 +172,7 @@ public class SaSsoUtil { } /** - * 构建URL:Server端向Client下放ticke的地址 + * 构建URL:Server端向Client下放ticket的地址 * @param loginId 账号id * @param client 客户端标识 * @param redirect Client端提供的重定向地址 @@ -172,16 +183,25 @@ public class SaSsoUtil { } /** - * 构建URL:Server端 账号资料查询地址 - * @param loginId 账号id - * @return Server端 账号资料查询地址 + * 构建URL:Server端 getData 地址,带签名等参数 + * @param paramMap 查询参数 + * @return / */ - public static String buildUserinfoUrl(Object loginId) { - return ssoTemplate.buildUserinfoUrl(loginId); + public static String buildGetDataUrl(Map paramMap) { + return ssoTemplate.buildGetDataUrl(paramMap); } + + /** + * 构建URL:Server 端自定义 path 地址,带签名等参数 (此方法需要配置 sa-token.sso.server-url 地址) + * @param paramMap 请求参数 + * @return / + */ + public static String buildCustomPathUrl(String path, Map paramMap) { + return ssoTemplate.buildCustomPathUrl(path, paramMap); + } + - - // ------------------- 请求相关 ------------------- + // ------------------- 发起请求 ------------------- /** * 发出请求,并返回 SaResult 结果 @@ -193,50 +213,46 @@ public class SaSsoUtil { } /** - * 校验secretkey秘钥是否有效 (API已过期,请更改为更安全的 sign 式校验) - * @param secretkey 秘钥 + * 给 paramMap 追加 sign 等参数,并序列化为kv字符串,拼接到url后面 + * @param url 请求地址 + * @param paramMap 请求原始参数列表 + * @return 加工后的url + */ + public static String joinParamMapAndSign(String url, Map paramMap) { + return ssoTemplate.joinLoginIdAndSign(url, paramMap); + } + + /** + * 给 url 拼接 loginId 参数,并拼接 sign 等参数 + * @param url 链接 + * @param loginId 账号id + * @return 加工后的url + */ + public static String joinLoginIdAndSign(String url, Object loginId) { + return ssoTemplate.joinLoginIdAndSign(url, loginId); + } + + + // -------- 以下方法已废弃,仅为兼容旧版本而保留 -------- + + /** + * 构建URL:Server端 账号资料查询地址 + * @param loginId 账号id + * @return Server端 账号资料查询地址 */ @Deprecated - public static void checkSecretkey(String secretkey) { - ssoTemplate.checkSecretkey(secretkey); + public static String buildUserinfoUrl(Object loginId) { + return ssoTemplate.buildUserinfoUrl(loginId); } /** - * 根据参数计算签名 + * 获取:账号资料 * @param loginId 账号id - * @param timestamp 当前时间戳,13位 - * @param nonce 随机字符串 - * @param secretkey 账号id - * @return 签名 + * @return 账号资料 */ - public static String getSign(Object loginId, String timestamp, String nonce, String secretkey) { - return ssoTemplate.getSign(loginId, timestamp, nonce, secretkey); + @Deprecated + public static Object getUserinfo(Object loginId) { + return ssoTemplate.getUserinfo(loginId); } - /** - * 给 url 追加 sign 等参数 - * @param url 连接 - * @param loginId 账号id - * @return 加工后的url - */ - public static String addSignParams(String url, Object loginId) { - return ssoTemplate.addSignParams(url, loginId); - } - - /** - * 校验签名 - * @param req request - */ - public static void checkSign(SaRequest req) { - ssoTemplate.checkSign(req); - } - - /** - * 校验时间戳与当前时间的差距是否超出限制 - * @param timestamp 时间戳 - */ - public static void checkTimestamp(long timestamp) { - ssoTemplate.checkTimestamp(timestamp); - } - } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java index ce2eedb7..0763086f 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/error/SaSsoErrorCode.java @@ -40,5 +40,8 @@ public interface SaSsoErrorCode { /** 该 ticket 不属于当前 client */ public static final int CODE_30011 = 30011; - + + /** 当前缺少配置 server-url 地址 */ + public static final int CODE_30012 = 30012; + } diff --git a/sa-token-starter/sa-token-jakarta-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java b/sa-token-starter/sa-token-jakarta-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java index 0bf4c04d..bd3ef2e1 100644 --- a/sa-token-starter/sa-token-jakarta-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java +++ b/sa-token-starter/sa-token-jakarta-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java @@ -1,7 +1,5 @@ package cn.dev33.satoken.servlet.model; -import java.io.IOException; - import cn.dev33.satoken.SaManager; import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.exception.SaTokenException; @@ -12,6 +10,9 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + /** * Request for Jakarta Servlet * @author kong @@ -48,6 +49,36 @@ public class SaRequestForServlet implements SaRequest { return request.getParameter(name); } + /** + * 获取 [请求体] 里提交的所有参数名称 + * @return 参数名称列表 + */ + @Override + public List getParamNames(){ + Enumeration parameterNames = request.getParameterNames(); + List list = new ArrayList<>(); + while (parameterNames.hasMoreElements()) { + list.add(parameterNames.nextElement()); + } + return list; + } + + /** + * 获取 [请求体] 里提交的所有参数 + * @return 参数列表 + */ + @Override + public Map getParamMap(){ + // 获取所有参数 + Map parameterMap = request.getParameterMap(); + Map map = new LinkedHashMap<>(parameterMap.size()); + for (String key : parameterMap.keySet()) { + String[] values = parameterMap.get(key); + map.put(key, values[0]); + } + return map; + } + /** * 在 [请求头] 里获取一个值 */ diff --git a/sa-token-starter/sa-token-jboot-plugin/src/main/java/cn/dev33/satoken/jboot/SaTokenPathFilter.java b/sa-token-starter/sa-token-jboot-plugin/src/main/java/cn/dev33/satoken/jboot/SaTokenPathFilter.java index c8de6c95..e20c03fd 100644 --- a/sa-token-starter/sa-token-jboot-plugin/src/main/java/cn/dev33/satoken/jboot/SaTokenPathFilter.java +++ b/sa-token-starter/sa-token-jboot-plugin/src/main/java/cn/dev33/satoken/jboot/SaTokenPathFilter.java @@ -91,8 +91,8 @@ public class SaTokenPathFilter implements SaFilter { public void doFilter(Controller ctx, FilterChain chain) throws Throwable { try { // 执行全局过滤器 + beforeAuth.run(null); SaRouter.match(includeList).notMatch(excludeList).check(r -> { - beforeAuth.run(null); auth.run(null); }); diff --git a/sa-token-starter/sa-token-jfinal-plugin/src/main/java/cn/dev33/satoken/jfinal/SaTokenPathFilter.java b/sa-token-starter/sa-token-jfinal-plugin/src/main/java/cn/dev33/satoken/jfinal/SaTokenPathFilter.java index 2444422e..078164a4 100644 --- a/sa-token-starter/sa-token-jfinal-plugin/src/main/java/cn/dev33/satoken/jfinal/SaTokenPathFilter.java +++ b/sa-token-starter/sa-token-jfinal-plugin/src/main/java/cn/dev33/satoken/jfinal/SaTokenPathFilter.java @@ -91,8 +91,8 @@ public class SaTokenPathFilter implements SaFilter { public void doFilter(Controller ctx, FilterChain chain) throws Throwable { try { // 执行全局过滤器 + beforeAuth.run(null); SaRouter.match(includeList).notMatch(excludeList).check(r -> { - beforeAuth.run(null); auth.run(null); }); diff --git a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java index f48d150d..b9ec70e5 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java +++ b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java @@ -119,9 +119,9 @@ public class SaReactorFilter implements SaFilter, WebFilter { // 写入全局上下文 (同步) SaReactorSyncHolder.setContext(exchange); - // 执行全局过滤器 + // 执行全局过滤器 + beforeAuth.run(null); SaRouter.match(includeList).notMatch(excludeList).check(r -> { - beforeAuth.run(null); auth.run(null); }); diff --git a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java index da98532e..35e58005 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java +++ b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java @@ -12,6 +12,8 @@ import cn.dev33.satoken.reactor.context.SaReactorHolder; import cn.dev33.satoken.reactor.context.SaReactorSyncHolder; import cn.dev33.satoken.util.SaFoxUtil; +import java.util.*; + /** * Request for Reactor * @author kong @@ -48,6 +50,25 @@ public class SaRequestForReactor implements SaRequest { return request.getQueryParams().getFirst(name); } + /** + * 获取 [请求体] 里提交的所有参数名称 + * @return 参数名称列表 + */ + @Override + public List getParamNames(){ + Set names = request.getQueryParams().keySet(); + return new ArrayList<>(names); + } + + /** + * 获取 [请求体] 里提交的所有参数 + * @return 参数列表 + */ + @Override + public Map getParamMap(){ + return request.getQueryParams().toSingleValueMap(); + } + /** * 在 [请求头] 里获取一个值 */ diff --git a/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java b/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java index a3287b31..07207108 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java +++ b/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java @@ -119,9 +119,9 @@ public class SaReactorFilter implements SaFilter, WebFilter { // 写入全局上下文 (同步) SaReactorSyncHolder.setContext(exchange); - // 执行全局过滤器 + // 执行全局过滤器 + beforeAuth.run(null); SaRouter.match(includeList).notMatch(excludeList).check(r -> { - beforeAuth.run(null); auth.run(null); }); diff --git a/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java b/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java index da98532e..da8819c0 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java +++ b/sa-token-starter/sa-token-reactor-spring-boot3-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java @@ -12,6 +12,11 @@ import cn.dev33.satoken.reactor.context.SaReactorHolder; import cn.dev33.satoken.reactor.context.SaReactorSyncHolder; import cn.dev33.satoken.util.SaFoxUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Request for Reactor * @author kong @@ -48,6 +53,25 @@ public class SaRequestForReactor implements SaRequest { return request.getQueryParams().getFirst(name); } + /** + * 获取 [请求体] 里提交的所有参数名称 + * @return 参数名称列表 + */ + @Override + public List getParamNames(){ + Set names = request.getQueryParams().keySet(); + return new ArrayList<>(names); + } + + /** + * 获取 [请求体] 里提交的所有参数 + * @return 参数列表 + */ + @Override + public Map getParamMap(){ + return request.getQueryParams().toSingleValueMap(); + } + /** * 在 [请求头] 里获取一个值 */ diff --git a/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java b/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java index 374bd25a..46b457c8 100644 --- a/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java +++ b/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java @@ -1,6 +1,7 @@ package cn.dev33.satoken.servlet.model; import java.io.IOException; +import java.util.*; import javax.servlet.ServletException; import javax.servlet.http.Cookie; @@ -49,6 +50,36 @@ public class SaRequestForServlet implements SaRequest { return request.getParameter(name); } + /** + * 获取 [请求体] 里提交的所有参数名称 + * @return 参数名称列表 + */ + @Override + public List getParamNames(){ + Enumeration parameterNames = request.getParameterNames(); + List list = new ArrayList<>(); + while (parameterNames.hasMoreElements()) { + list.add(parameterNames.nextElement()); + } + return list; + } + + /** + * 获取 [请求体] 里提交的所有参数 + * @return 参数列表 + */ + @Override + public Map getParamMap(){ + // 获取所有参数 + Map parameterMap = request.getParameterMap(); + Map map = new LinkedHashMap<>(parameterMap.size()); + for (String key : parameterMap.keySet()) { + String[] values = parameterMap.get(key); + map.put(key, values[0]); + } + return map; + } + /** * 在 [请求头] 里获取一个值 */ diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/integration/SaTokenFilter.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/integration/SaTokenFilter.java index 6a4698f6..5331ab03 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/integration/SaTokenFilter.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/integration/SaTokenFilter.java @@ -123,12 +123,13 @@ public class SaTokenFilter implements SaFilter, Filter { //之所以改名,为 Handler mainHandler = Solon.app().router().matchMain(ctx); Action action = (mainHandler instanceof Action ? (Action) mainHandler : null); + //1.执行前置处理(主要是一些跨域之类的) + if(beforeAuth != null) { + beforeAuth.run(mainHandler); + } + //先路径过滤下(包括了静态文件) SaRouter.match(includeList).notMatch(excludeList).check(r -> { - //1.执行前置处理(主要是一些跨域之类的) - if(beforeAuth != null) { - beforeAuth.run(mainHandler); - } //2.执行注解处理 if(authAnno(action)) { //3.执行规则处理(如果没有被 @SaIgnore 忽略) diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/integration/SaTokenInterceptor.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/integration/SaTokenInterceptor.java index 0afb5a82..a9554c76 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/integration/SaTokenInterceptor.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/integration/SaTokenInterceptor.java @@ -172,19 +172,19 @@ public class SaTokenInterceptor implements RouterInterceptor { try { Action action = (mainHandler instanceof Action ? (Action) mainHandler : null); - //先路径过滤下(包括了静态文件) + //1.执行前置处理(主要是一些跨域之类的) + if(beforeAuth != null) { + beforeAuth.run(mainHandler); + } + + //先路径过滤下(不包括静态文件) SaRouter.match(includeList).notMatch(excludeList).check(r -> { - //1.执行前置处理(主要是一些跨域之类的) - if(beforeAuth != null) { - beforeAuth.run(mainHandler); - } //2.执行注解处理 if(authAnno(action)) { //3.执行规则处理(如果没有被 @SaIgnore 忽略) auth.run(mainHandler); } }); - } catch (StopMatchException e) { } catch (SaTokenException e) { diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java index ae66fd12..7cf25fb1 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java @@ -5,6 +5,11 @@ import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.util.SaFoxUtil; import org.noear.solon.core.handle.Context; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * @author noear * @since 1.4 @@ -27,6 +32,21 @@ public class SaRequestForSolon implements SaRequest { return ctx.param(s); } + @Override + public List getParamNames(){ + Set names = ctx.paramMap().keySet(); + return new ArrayList<>(names); + } + + /** + * 获取 [请求体] 里提交的所有参数 + * @return 参数列表 + */ + @Override + public Map getParamMap(){ + return ctx.paramMap(); + } + @Override public String getHeader(String s) { return ctx.header(s); diff --git a/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/filter/SaServletFilter.java b/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/filter/SaServletFilter.java index ddd54d7d..15875222 100644 --- a/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/filter/SaServletFilter.java +++ b/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/filter/SaServletFilter.java @@ -111,9 +111,9 @@ public class SaServletFilter implements SaFilter, Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { - // 执行全局过滤器 + // 执行全局过滤器 + beforeAuth.run(null); SaRouter.match(includeList).notMatch(excludeList).check(r -> { - beforeAuth.run(null); auth.run(null); }); diff --git a/sa-token-starter/sa-token-spring-boot3-starter/src/main/java/cn/dev33/satoken/filter/SaServletFilter.java b/sa-token-starter/sa-token-spring-boot3-starter/src/main/java/cn/dev33/satoken/filter/SaServletFilter.java index 9255993c..98aa8f0c 100644 --- a/sa-token-starter/sa-token-spring-boot3-starter/src/main/java/cn/dev33/satoken/filter/SaServletFilter.java +++ b/sa-token-starter/sa-token-spring-boot3-starter/src/main/java/cn/dev33/satoken/filter/SaServletFilter.java @@ -110,9 +110,9 @@ public class SaServletFilter implements SaFilter, Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { - // 执行全局过滤器 + // 执行全局过滤器 + beforeAuth.run(null); SaRouter.match(includeList).notMatch(excludeList).check(r -> { - beforeAuth.run(null); auth.run(null); });