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 2ba3b674..e23e9e31 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 @@ -15,6 +15,8 @@ import cn.dev33.satoken.json.SaJsonTemplate; import cn.dev33.satoken.json.SaJsonTemplateDefaultImpl; import cn.dev33.satoken.listener.SaTokenListener; import cn.dev33.satoken.listener.SaTokenListenerDefaultImpl; +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; @@ -191,6 +193,24 @@ public class SaManager { } return saJsonTemplate; } + + /** + * 参数签名 Bean + */ + private volatile static SaSignTemplate saSignTemplate; + public static void setSaSignTemplate(SaSignTemplate saSignTemplate) { + SaManager.saSignTemplate = saSignTemplate; + } + public static SaSignTemplate getSaSignTemplate() { + if (saSignTemplate == null) { + synchronized (SaManager.class) { + if (saSignTemplate == null) { + setSaSignTemplate(new SaSignTemplateDefaultImpl()); + } + } + } + return saSignTemplate; + } /** * StpLogic集合, 记录框架所有成功初始化的StpLogic 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 new file mode 100644 index 00000000..2bfc97c5 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplate.java @@ -0,0 +1,59 @@ +package cn.dev33.satoken.sign; + +import java.util.Map; +import java.util.TreeMap; + +import cn.dev33.satoken.secure.SaSecureUtil; +import cn.dev33.satoken.util.SaFoxUtil; + +/** + * 参数签名算法 + * + * @author kong + * @date: 2022-4-27 + */ +public interface SaSignTemplate { + + /** + * 将所有参数连接成一个字符串,形如: k1=v1&k2=v2&k3=v3 + * @param paramsMap 参数列表 + * @return 字符串 + */ + public default String joinParams(Map paramsMap) { + // 保证字段按照字典顺序排列 + if(paramsMap instanceof TreeMap == false) { + paramsMap = new TreeMap<>(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) { + sb.append(key).append("=").append(value).append("&"); + } + } + + // 删除最后一位 & + if(sb.length() > 0) { + sb.deleteCharAt(sb.length() - 1); + } + + // . + return sb.toString(); + } + + /** + * 创建签名:md5(paramsStr + keyStr) + * @param paramsMap 参数列表 + * @param key 秘钥 + * @return + */ + public default String createSign(Map paramsMap, String key) { + String paramsStr = joinParams(paramsMap); + String fullStr = paramsStr + "&key=" + key; + return SaSecureUtil.md5(fullStr); + } + + +} 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 new file mode 100644 index 00000000..b1159cdc --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/sign/SaSignTemplateDefaultImpl.java @@ -0,0 +1,11 @@ +package cn.dev33.satoken.sign; + +/** + * 参数签名算法 [默认实现类] + * + * @author kong + * @date: 2022-4-27 + */ +public class SaSignTemplateDefaultImpl implements SaSignTemplate { + +} diff --git a/sa-token-demo/sa-token-demo-sso-client-h5/sso-login.html b/sa-token-demo/sa-token-demo-sso-client-h5/sso-login.html index 59092924..4191fe89 100644 --- a/sa-token-demo/sa-token-demo-sso-client-h5/sso-login.html +++ b/sa-token-demo/sa-token-demo-sso-client-h5/sso-login.html @@ -43,6 +43,8 @@ if(res.code == 200) { localStorage.setItem('satoken', res.data); location.href = decodeURIComponent(back); + } else { + alert(res.msg); } }) } diff --git a/sa-token-demo/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java b/sa-token-demo/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java index c3bab92e..954d942c 100644 --- a/sa-token-demo/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java +++ b/sa-token-demo/sa-token-demo-sso-server/src/main/java/com/pj/sso/SsoServerController.java @@ -53,8 +53,15 @@ public class SsoServerController { // 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉) sso.setSendHttp(url -> { - return OkHttps.sync(url).get().getBody().toString(); + try { + // 发起 http 请求 + System.out.println("发起请求:" + url); + return OkHttps.sync(url).get().getBody().toString(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } }); } - + } diff --git a/sa-token-demo/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java b/sa-token-demo/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java index 2c124caf..e7f6dd09 100644 --- a/sa-token-demo/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java +++ b/sa-token-demo/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java @@ -46,6 +46,7 @@ public class SsoClientController { private void configSso(SaSsoConfig sso) { // 配置Http请求处理器 sso.setSendHttp(url -> { + System.out.println("发起请求:" + url); return OkHttps.sync(url).get().getBody().toString(); }); } diff --git a/sa-token-doc/doc/sso/sso-apidoc.md b/sa-token-doc/doc/sso/sso-apidoc.md index e1acc5e7..d215913c 100644 --- a/sa-token-doc/doc/sso/sso-apidoc.md +++ b/sa-token-doc/doc/sso/sso-apidoc.md @@ -83,22 +83,33 @@ http://{host}:{port}/sso/checkTicket http://{host}:{port}/sso/logout ``` -接受参数: - -| 参数 | 是否必填 | 说明 | -| :-------- | :-------- | :-------- | -| loginId | 否 | 要注销的账号id | -| secretkey | 否 | 接口通信秘钥 | -| back | 否 | 注销成功后的重定向地址 | - - 此接口有两种调用方式 -##### 方式一:在 Client 的前端页面引导用户直接跳转,并带有 back 参数 -例如:`http://{host}:{port}/sso/logout?back=xxx`,代表用户注销成功后返回back地址 +##### 4.1、方式一:在 Client 的前端页面引导用户直接跳转,并带有 back 参数 +例如: -##### 方式二:在 Client 的后端通过 http 工具来调用 -例如:`http://{host}:{port}/sso/logout?loginId={value}&secretkey={value}`,代表注销 账号=loginId 的账号,返回json数据结果,形如: +``` url +http://{host}:{port}/sso/logout?back=xxx +``` +用户注销成功后将返回 back 地址 + +##### 4.2、方式二:在 Client 的后端通过 http 工具来调用 + +接受参数: + +| 参数 | 是否必填 | 说明 | +| :-------- | :-------- | :-------- | +| loginId | 是 | 要注销的账号 id | +| timestamp | 是 | 当前时间戳,13位 | +| nonce | 是 | 随机字符串 | +| sign | 是 | 签名,生成算法:`md5( loginId={value}&nonce={value}×tamp={value}&key={secretkey秘钥} )` | + +例如: +``` url +http://{host}:{port}/sso/logout?loginId={value}×tamp={value}&nonce={value}&sign={value} +``` + +将返回 json 数据结果,形如: ``` js { @@ -113,7 +124,7 @@ http://{host}:{port}/sso/logout ``` js { "code": 500, // 200表示请求成功,非200标识请求失败 - "msg": "无效秘钥:xxx", // 失败原因 + "msg": "签名无效:xxx", // 失败原因 "data": null } ``` diff --git a/sa-token-doc/doc/sso/sso-questions.md b/sa-token-doc/doc/sso/sso-questions.md index 442f241a..0400f3e9 100644 --- a/sa-token-doc/doc/sso/sso-questions.md +++ b/sa-token-doc/doc/sso/sso-questions.md @@ -5,9 +5,11 @@ ### 问:在模式一与模式二中,Client端 必须通过 Alone-Redis 插件来访问Redis吗? 答:不必须,只是推荐,权限缓存与业务缓存分离后会减少 `SSO-Redis` 的访问压力,且可以避免多个 `Client端` 的缓存读写冲突 + ### 问:将旧有系统改造为单点登录时,应该注意哪些? 答:建议不要把其中一个系统改造为SSO服务端,而是新起一个项目作为Server端,所有旧有项目全部作为Client端与此对接 + ### 问:SSO模式二,第一个域名登录成功之后其他两个不会自动登录? 答:系统1登录成功之后,系统二与系统三需要点击登录按钮,才会登录成功 @@ -15,13 +17,23 @@ > 第二个系统,需要:点击 [登录] 按钮 -> 登录成功
> 第三个系统,需要:点击 [登录] 按钮 -> 登录成功 (免去重复跳转登录页输入账号密码的步骤) + ### 追问:那我是否可以设计成不需要点登录按钮的,只要访问页面,它就能登录成功 可以:加个过滤器检测到未登录 自动跳转就行了,详细可以参照章节:[[何时引导用户去登录]](/sso/sso-custom-login) 给出的建议进行设计 + ### 问:我参照文档的SSO模式二搭建,一直提示:Ticket无效,请问怎么回事? 根据群友的反馈,出现此异常概率最大的原因是因为 `Client` 与 `Server` 没有连接同一个Redis,SSO模式二中两者必须连接同一个 Redis 才可以登录成功, 如果您排查之后不是此原因,可以加入QQ群或者在issues反馈一下 + +### 模式一或者模式二报错:Could not write JSON: No serializer found for class com.pj.sso.SysUser and no properties discovered to create BeanSerializer + +一般是因为在 sso-server 端往 session 上写入了某个实体类(比如 User),而在 sso-client 端没有这个实体类,导致反序列化失败。 + +解决方案:在 sso-client 也新建上这个类,而且包名需要与 sso-server 端的一致(直接从 sso-server 把实体类复制过来就好了) + + ### 还有其它问题? 可以加群反馈一下,比较典型的问题我们解决之后都会提交到此页面方便大家快速排查 diff --git a/sa-token-doc/doc/sso/sso-server.md b/sa-token-doc/doc/sso/sso-server.md index 8a5103ab..b8514cb0 100644 --- a/sa-token-doc/doc/sso/sso-server.md +++ b/sa-token-doc/doc/sso/sso-server.md @@ -93,14 +93,23 @@ public class SsoServerController { // 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉) cfg.sso.setSendHttp(url -> { - return OkHttps.sync(url).get().getBody().toString(); + try { + // 发起 http 请求 + System.out.println("发起请求:" + url); + return OkHttps.sync(url).get().getBody().toString(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } }); } } ``` -注:在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取 +注意: +- 在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取 +- 在 `setSendHttp` 函数中,使用 `try-catch` 是为了提高整个注销流程的容错性,避免在一些极端情况下注销失败(例如:某个 Client 端上线之后又下线,导致 http 请求无法调用成功,从而阻断了整个注销流程) 全局异常处理: ``` java diff --git a/sa-token-doc/doc/sso/sso-type3.md b/sa-token-doc/doc/sso/sso-type3.md index 8f9c58cb..aef3eef5 100644 --- a/sa-token-doc/doc/sso/sso-type3.md +++ b/sa-token-doc/doc/sso/sso-type3.md @@ -41,6 +41,7 @@ private void configSso(SaTokenConfig cfg) { // 配置 Http 请求处理器 cfg.sso.setSendHttp(url -> { + System.out.println("发起请求:" + url); return OkHttps.sync(url).get().getBody().toString(); }); } @@ -70,12 +71,12 @@ sa-token: ``` java // 自定义接口:获取userinfo @RequestMapping("/sso/userinfo") -public Object userinfo(String loginId, String secretkey) { +public Object userinfo(String loginId) { System.out.println("---------------- 获取userinfo --------"); - // 校验调用秘钥 - SaSsoUtil.checkSecretkey(secretkey); - + // 校验签名,防止敏感信息外泄 + SaSsoUtil.checkSign(SaHolder.getRequest()); + // 自定义返回结果(模拟) return SaResult.ok() .set("id", loginId) diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java index 3c3c234f..80dc2b02 100644 --- a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtUtil.java @@ -4,7 +4,6 @@ import java.util.Map; import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.exception.NotLoginException; -import cn.dev33.satoken.exception.SaTokenException; import cn.dev33.satoken.util.SaFoxUtil; import cn.hutool.json.JSONObject; import cn.hutool.jwt.JWT; @@ -53,9 +52,6 @@ public class SaJwtUtil { */ public static String createToken(Object loginId, Map extraData, String keyt) { - // 秘钥不可以为空 - SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); - // 构建 String token = JWT.create() .setPayload(LOGIN_ID, loginId) @@ -82,9 +78,6 @@ public class SaJwtUtil { public static String createToken(String loginType, Object loginId, String device, long timeout, Map extraData, String keyt) { - // 秘钥不可以为空 - SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); - // 计算有效期 long effTime = timeout; if(timeout != NEVER_EXPIRE) { @@ -112,9 +105,7 @@ public class SaJwtUtil { * @return 解析后的jwt 对象 */ public static JWT parseToken(String token, String keyt) { - // 秘钥不可以为空 - SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); - + // 如果token为null if(token == null) { throw NotLoginException.newInstance(null, NotLoginException.NOT_TOKEN); diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMix.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMix.java index 180180fd..80d795f6 100644 --- a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMix.java +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForMix.java @@ -7,6 +7,7 @@ import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.exception.ApiDisabledException; import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.SaTokenException; import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpUtil; @@ -38,7 +39,9 @@ public class StpLogicJwtForMix extends StpLogic { * @return / */ public String jwtSecretKey() { - return getConfig().getJwtSecretKey(); + String keyt = getConfig().getJwtSecretKey(); + SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); + return keyt; } // diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStateless.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStateless.java index 8f571e9a..785114f7 100644 --- a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStateless.java +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStateless.java @@ -40,7 +40,9 @@ public class StpLogicJwtForStateless extends StpLogic { * @return / */ public String jwtSecretKey() { - return getConfig().getJwtSecretKey(); + String keyt = getConfig().getJwtSecretKey(); + SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); + return keyt; } // diff --git a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStyle.java b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStyle.java index 638dde82..dfed3d54 100644 --- a/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStyle.java +++ b/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/StpLogicJwtForStyle.java @@ -2,6 +2,7 @@ package cn.dev33.satoken.jwt; import java.util.Map; +import cn.dev33.satoken.exception.SaTokenException; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpUtil; @@ -32,7 +33,9 @@ public class StpLogicJwtForStyle extends StpLogic { * @return / */ public String jwtSecretKey() { - return getConfig().getJwtSecretKey(); + String keyt = getConfig().getJwtSecretKey(); + SaTokenException.throwByNull(keyt, "请配置jwt秘钥"); + return keyt; } // ------ 重写方法 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 c935401e..563d8909 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 @@ -91,6 +91,14 @@ public class SaSsoConfig implements Serializable { public String ssoLogoutCall; + // ----------------- 其它 + + /** + * 接口调用时的时间戳允许的差距(单位:ms),-1代表不校验差距 + */ + public long timestampDisparity = 1000 * 60 * 10; + + /** @@ -253,12 +261,37 @@ 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 + ", authUrl=" + authUrl + ", checkTicketUrl=" - + checkTicketUrl + ", userinfoUrl=" + userinfoUrl + ", sloUrl=" + sloUrl + ", ssoLogoutCall=" - + ssoLogoutCall + "]"; + return "SaSsoConfig [" + + "ticketTimeout=" + ticketTimeout + + ", allowUrl=" + allowUrl + + ", isSlo=" + isSlo + + ", isHttp=" + isHttp + + ", secretkey=" + secretkey + + ", authUrl=" + authUrl + + ", checkTicketUrl=" + checkTicketUrl + + ", userinfoUrl=" + userinfoUrl + + ", sloUrl=" + sloUrl + + ", ssoLogoutCall=" + ssoLogoutCall + + ", timestampDisparity=" + timestampDisparity + + "]"; } /** diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoConsts.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoConsts.java index c825db1f..f456f82e 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoConsts.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoConsts.java @@ -62,6 +62,10 @@ public class SaSsoConsts { public static String name = "name"; public static String pwd = "pwd"; + + public static String timestamp = "timestamp"; + public static String nonce = "nonce"; + public static String sign = "sign"; } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java index ba2cdc8e..8a864d43 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java @@ -1,8 +1,5 @@ package cn.dev33.satoken.sso; -import java.util.Map; - -import cn.dev33.satoken.SaManager; import cn.dev33.satoken.config.SaSsoConfig; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.model.SaRequest; @@ -110,15 +107,13 @@ public class SaSsoHandle { } /** - * SSO-Server端:校验ticket 获取账号id + * SSO-Server端:校验ticket 获取账号id [模式三] * @return 处理结果 */ public static Object ssoCheckTicket() { - // 获取对象 - SaRequest req = SaHolder.getRequest(); - // 获取参数 - String ticket = req.getParam(ParamName.ticket); + SaRequest req = SaHolder.getRequest(); + String ticket = req.getParamNotNull(ParamName.ticket); String sloCallback = req.getParam(ParamName.ssoLogoutCall); // 校验ticket,获取 loginId @@ -143,15 +138,12 @@ public class SaSsoHandle { // 获取对象 SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); - SaSsoConfig cfg = SaSsoManager.getConfig(); - StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; - String loginId = req.getParam(ParamName.loginId); + Object loginId = SaSsoUtil.saSsoTemplate.stpLogic.getLoginIdDefaultNull(); - // step.1 遍历 Client 端注销 - SaSsoUtil.forEachSloUrl(loginId, url -> cfg.getSendHttp().apply(url)); - - // step.2 Server 端注销 - stpLogic.logout(); + // 单点注销 + if(SaFoxUtil.isNotEmpty(loginId)) { + SaSsoUtil.ssoLogout(loginId); + } // 完成 return ssoLogoutBack(req, res); @@ -162,25 +154,17 @@ public class SaSsoHandle { * @return 处理结果 */ public static Object ssoLogoutByClientHttp() { - // 获取对象 - SaRequest req = SaHolder.getRequest(); - SaSsoConfig cfg = SaSsoManager.getConfig(); - StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; - // 获取参数 + SaRequest req = SaHolder.getRequest(); String loginId = req.getParam(ParamName.loginId); - String secretkey = req.getParam(ParamName.secretkey); - // step.1 校验秘钥 - SaSsoUtil.checkSecretkey(secretkey); + // step.1 校验签名 + SaSsoUtil.checkSign(req); - // step.2 遍历 Client 端注销 - SaSsoUtil.forEachSloUrl(loginId, url -> cfg.getSendHttp().apply(url)); - - // step.3 Server 端注销 - stpLogic.logout(loginId); - - // 完成 + // step.2 单点注销 + SaSsoUtil.ssoLogout(loginId); + + // 响应 return SaResult.ok(); } @@ -302,12 +286,16 @@ public class SaSsoHandle { return SaResult.ok(); } - // 调用SSO-Server认证中心API,进行注销 + // 调用 sso-server 认证中心单点注销API String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId()); - SaResult result = request(url); + SaResult result = SaSsoUtil.request(url); - // 校验 + // 校验响应状态码 if(result.getCode() == SaResult.CODE_SUCCESS) { + // 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀 + if(stpLogic.isLogin()) { + stpLogic.logout(); + } return ssoLogoutBack(req, res); } else { // 将 sso-server 回应的消息作为异常抛出 @@ -325,11 +313,10 @@ public class SaSsoHandle { StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; // 获取参数 - String loginId = req.getParam(ParamName.loginId); - String secretkey = req.getParam(ParamName.secretkey); + String loginId = req.getParamNotNull(ParamName.loginId); // 注销当前应用端会话 - SaSsoUtil.checkSecretkey(secretkey); + SaSsoUtil.checkSign(req); stpLogic.logout(loginId); // 响应 @@ -382,7 +369,7 @@ public class SaSsoHandle { // 发起请求 String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall); - SaResult result = request(checkUrl); + SaResult result = SaSsoUtil.request(checkUrl); // 校验 if(result.getCode() == SaResult.CODE_SUCCESS) { @@ -397,15 +384,4 @@ public class SaSsoHandle { } } - /** - * 发出请求,并返回 SaResult 结果 - * @param url 请求地址 - * @return 返回的结果 - */ - public static SaResult request(String url) { - String body = SaSsoManager.getConfig().getSendHttp().apply(url); - Map map = SaManager.getSaJsonTemplate().parseJsonToMap(body); - return new SaResult(map); - } - } 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 8009faf5..756c384a 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 @@ -3,10 +3,13 @@ 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.sso.SaSsoConsts.ParamName; import cn.dev33.satoken.sso.exception.SaSsoException; @@ -14,6 +17,7 @@ import cn.dev33.satoken.sso.exception.SaSsoExceptionCode; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.util.SaFoxUtil; +import cn.dev33.satoken.util.SaResult; /** * Sa-Token-SSO 单点登录模块 @@ -149,6 +153,94 @@ public class SaSsoTemplate { return SaFoxUtil.getRandomString(64); } + /** + * 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) + * @return see note + */ + public String getAllowUrl() { + // 默认从配置文件中返回 + return SaSsoManager.getConfig().getAllowUrl(); + } + + /** + * 校验重定向url合法性 + * @param url 下放ticket的url地址 + */ + public void checkRedirectUrl(String url) { + + // 1、是否是一个有效的url + if(SaFoxUtil.isUrl(url) == false) { + throw new SaSsoException("无效redirect:" + url).setCode(SaSsoExceptionCode.CODE_20001); + } + + // 2、截取掉?后面的部分 + int qIndex = url.indexOf("?"); + if(qIndex != -1) { + url = url.substring(0, qIndex); + } + + // 3、是否在[允许地址列表]之中 + List authUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(",")); + if(SaStrategy.me.hasElement.apply(authUrlList, url) == false) { + throw new SaSsoException("非法redirect:" + url).setCode(SaSsoExceptionCode.CODE_20002); + } + + // 校验通过 √ + return; + } + + + // ------------------- SSO 模式三相关 ------------------- + + /** + * 为指定账号id注册单点注销回调URL + * @param loginId 账号id + * @param sloCallbackUrl 单点注销时的回调URL + */ + public void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) { + if(SaFoxUtil.isEmpty(loginId) || SaFoxUtil.isEmpty(sloCallbackUrl)) { + return; + } + SaSession session = stpLogic.getSessionByLoginId(loginId); + Set urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, ()-> new HashSet()); + urlSet.add(sloCallbackUrl); + session.set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet); + } + + /** + * 指定账号单点注销 + * @param loginId 指定账号 + */ + public void ssoLogout(Object loginId) { + + // 如果这个账号尚未登录,则无操作 + SaSession session = stpLogic.getSessionByLoginId(loginId, false); + if(session == null) { + return; + } + + // step.1 遍历通知 Client 端注销会话 + SaSsoConfig cfg = SaSsoManager.getConfig(); + Set urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, () -> new HashSet()); + for (String url : urlSet) { + url = addSignParams(url, loginId); + cfg.getSendHttp().apply(url); + } + + // step.2 Server端注销 + stpLogic.logout(loginId); + } + + /** + * 获取:账号资料 + * @param loginId 账号id + * @return 账号资料 + */ + public Object getUserinfo(Object loginId) { + String url = buildUserinfoUrl(loginId); + return SaSsoManager.getConfig().getSendHttp().apply(url); + } + // ---------------------- 构建URL ---------------------- @@ -202,43 +294,7 @@ public class SaSsoTemplate { // 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket) return SaFoxUtil.joinParam(encodeBackParam(redirect), ParamName.ticket, ticket); } - - /** - * 校验重定向url合法性 - * @param url 下放ticket的url地址 - */ - public void checkRedirectUrl(String url) { - - // 1、是否是一个有效的url - if(SaFoxUtil.isUrl(url) == false) { - throw new SaSsoException("无效redirect:" + url).setCode(SaSsoExceptionCode.CODE_20001); - } - - // 2、截取掉?后面的部分 - int qIndex = url.indexOf("?"); - if(qIndex != -1) { - url = url.substring(0, qIndex); - } - - // 3、是否在[允许地址列表]之中 - List authUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(",")); - if(SaStrategy.me.hasElement.apply(authUrlList, url) == false) { - throw new SaSsoException("非法redirect:" + url).setCode(SaSsoExceptionCode.CODE_20002); - } - - // 校验通过 √ - return; - } - - /** - * 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) - * @return see note - */ - public String getAllowUrl() { - // 默认从配置文件中返回 - return SaSsoManager.getConfig().getAllowUrl(); - } - + /** * 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug * @param url url @@ -271,27 +327,10 @@ public class SaSsoTemplate { * @return Server端 账号资料查询地址 */ public String buildUserinfoUrl(Object loginId) { - // 拼接 String userinfoUrl = SaSsoManager.getConfig().getUserinfoUrl(); - userinfoUrl = SaFoxUtil.joinParam(userinfoUrl, ParamName.loginId, loginId); - userinfoUrl = SaFoxUtil.joinParam(userinfoUrl, ParamName.secretkey, SaSsoManager.getConfig().getSecretkey()); - // 返回 - return userinfoUrl; + return addSignParams(userinfoUrl, loginId); } - - // ------------------- SSO 模式三相关 ------------------- - - /** - * 校验secretkey秘钥是否有效 - * @param secretkey 秘钥 - */ - public void checkSecretkey(String secretkey) { - if(secretkey == null || secretkey.isEmpty() || secretkey.equals(SaSsoManager.getConfig().getSecretkey()) == false) { - throw new SaSsoException("无效秘钥:" + secretkey).setCode(SaSsoExceptionCode.CODE_20003); - } - } - /** * 构建URL:校验ticket的URL *

在模式三下,Client端拿到Ticket后根据此地址向Server端发送请求,获取账号id @@ -314,43 +353,6 @@ public class SaSsoTemplate { // 返回 return url; } - - /** - * 为指定账号id注册单点注销回调URL - * @param loginId 账号id - * @param sloCallbackUrl 单点注销时的回调URL - */ - public void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) { - if(loginId == null || sloCallbackUrl == null || sloCallbackUrl.isEmpty()) { - return; - } - SaSession session = stpLogic.getSessionByLoginId(loginId); - Set urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, ()-> new HashSet()); - urlSet.add(sloCallbackUrl); - session.set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet); - } - - /** - * 循环调用Client端单点注销回调 - * @param loginId 账号id - * @param fun 调用方法 - */ - public void forEachSloUrl(Object loginId, CallSloUrlFunction fun) { - SaSession session = stpLogic.getSessionByLoginId(loginId, false); - if(session == null) { - return; - } - - String secretkey = SaSsoManager.getConfig().getSecretkey(); - Set urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, () -> new HashSet()); - for (String url : urlSet) { - // 拼接:login参数、秘钥参数 - url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId); - url = SaFoxUtil.joinParam(url, ParamName.secretkey, secretkey); - // 调用 - fun.run(url); - } - } /** * 构建URL:单点注销URL @@ -358,42 +360,10 @@ public class SaSsoTemplate { * @return 单点注销URL */ public String buildSloUrl(Object loginId) { - SaSsoConfig ssoConfig = SaSsoManager.getConfig(); - String url = ssoConfig.getSloUrl(); - url = SaFoxUtil.joinParam(url, ParamName.loginId, loginId); - url = SaFoxUtil.joinParam(url, ParamName.secretkey, ssoConfig.getSecretkey()); - return url; - } - - /** - * 指定账号单点注销 - * @param secretkey 校验秘钥 - * @param loginId 指定账号 - * @param fun 调用方法 - */ - public void singleLogout(String secretkey, Object loginId, CallSloUrlFunction fun) { - // step.1 校验秘钥 - checkSecretkey(secretkey); - - // step.2 遍历通知Client端注销会话 - forEachSloUrl(loginId, fun); - - // step.3 Server端注销 - // StpUtil.logoutByLoginId(loginId); - stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId)); + String url = SaSsoManager.getConfig().getSloUrl(); + return addSignParams(url, loginId); } - /** - * 获取:账号资料 - * @param loginId 账号id - * @return 账号资料 - */ - public Object getUserinfo(Object loginId) { - String url = buildUserinfoUrl(loginId); - return SaSsoManager.getConfig().getSendHttp().apply(url); - } - - // ------------------- 返回相应key ------------------- @@ -415,17 +385,112 @@ public class SaSsoTemplate { return SaManager.getConfig().getTokenName() + ":id-ticket:" + id; } + + // ------------------- 请求相关 ------------------- + /** - * 单点注销回调函数 - * @author kong + * 发出请求,并返回 SaResult 结果 + * @param url 请求地址 + * @return 返回的结果 */ - @FunctionalInterface - public static interface CallSloUrlFunction{ - /** - * 调用function - * @param url 注销回调URL - */ - public void run(String url); + public SaResult request(String url) { + String body = SaSsoManager.getConfig().getSendHttp().apply(url); + Map map = SaManager.getSaJsonTemplate().parseJsonToMap(body); + return new SaResult(map); + } + + /** + * 获取:接口调用秘钥 + * @return see note + */ + public String getSecretkey() { + // 默认从配置文件中返回 + String secretkey = SaSsoManager.getConfig().getSecretkey(); + if(SaFoxUtil.isEmpty(secretkey)) { + throw new SaSsoException("请配置 secretkey 参数").setCode(SaSsoExceptionCode.CODE_20009); + } + return secretkey; + } + + /** + * 校验secretkey秘钥是否有效 (API已过期,请更改为更安全的 sign 式校验) + * @param secretkey 秘钥 + */ + @Deprecated + public void checkSecretkey(String secretkey) { + if(SaFoxUtil.isEmpty(secretkey) || secretkey.equals(getSecretkey()) == false) { + throw new SaSsoException("无效秘钥:" + secretkey).setCode(SaSsoExceptionCode.CODE_20003); + } + } + + /** + * 根据参数计算签名 + * @param loginId 账号id + * @param timestamp 当前时间戳,13位 + * @param nonce 随机字符串 + * @param secretkey 账号id + * @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); + } + + /** + * 给 url 追加 sign 等参数 + * @param loginId + * @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(SaSsoExceptionCode.CODE_20008); + } + } + + /** + * 校验时间戳与当前时间的差距是否超出限制 + * @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(SaSsoExceptionCode.CODE_20007); + } } } 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 a48bf95b..68097d6c 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,7 +1,8 @@ package cn.dev33.satoken.sso; -import cn.dev33.satoken.sso.SaSsoTemplate.CallSloUrlFunction; +import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; /** * Sa-Token-SSO 单点登录模块 工具类 @@ -71,7 +72,71 @@ public class SaSsoUtil { public static Object checkTicket(String ticket) { return saSsoTemplate.checkTicket(ticket); } + + /** + * 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) + * @return see note + */ + public static String getAllowUrl() { + return saSsoTemplate.getAllowUrl(); + } + + /** + * 校验重定向url合法性 + * @param url 下放ticket的url地址 + */ + public static void checkRedirectUrl(String url) { + saSsoTemplate.checkRedirectUrl(url); + } + + // ------------------- SSO 模式三 ------------------- + + /** + * 构建URL:校验ticket的URL + * @param ticket ticket码 + * @param ssoLogoutCallUrl 单点注销时的回调URL + * @return 构建完毕的URL + */ + public static String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) { + return saSsoTemplate.buildCheckTicketUrl(ticket, ssoLogoutCallUrl); + } + + /** + * 为指定账号id注册单点注销回调URL + * @param loginId 账号id + * @param sloCallbackUrl 单点注销时的回调URL + */ + public static void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) { + saSsoTemplate.registerSloCallbackUrl(loginId, sloCallbackUrl); + } + + /** + * 构建URL:单点注销URL + * @param loginId 要注销的账号id + * @return 单点注销URL + */ + public static String buildSloUrl(Object loginId) { + return saSsoTemplate.buildSloUrl(loginId); + } + + /** + * 指定账号单点注销 + * @param loginId 指定账号 + */ + public static void ssoLogout(Object loginId) { + saSsoTemplate.ssoLogout(loginId); + } + + /** + * 获取:账号资料 + * @param loginId 账号id + * @return 账号资料 + */ + public static Object getUserinfo(Object loginId) { + return saSsoTemplate.getUserinfo(loginId); + } + // ---------------------- 构建URL ---------------------- @@ -94,22 +159,6 @@ public class SaSsoUtil { public static String buildRedirectUrl(Object loginId, String redirect) { return saSsoTemplate.buildRedirectUrl(loginId, redirect); } - - /** - * 校验重定向url合法性 - * @param url 下放ticket的url地址 - */ - public static void checkRedirectUrl(String url) { - saSsoTemplate.checkRedirectUrl(url); - } - - /** - * 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) - * @return see note - */ - public static String getAllowUrl() { - return saSsoTemplate.getAllowUrl(); - } /** * 构建URL:Server端 账号资料查询地址 @@ -120,70 +169,62 @@ public class SaSsoUtil { return saSsoTemplate.buildUserinfoUrl(loginId); } - // ------------------- SSO 模式三 ------------------- + // ------------------- 请求相关 ------------------- + /** - * 校验secretkey秘钥是否有效 + * 发出请求,并返回 SaResult 结果 + * @param url 请求地址 + * @return 返回的结果 + */ + public static SaResult request(String url) { + return saSsoTemplate.request(url); + } + + /** + * 校验secretkey秘钥是否有效 (API已过期,请更改为更安全的 sign 式校验) * @param secretkey 秘钥 */ + @Deprecated public static void checkSecretkey(String secretkey) { saSsoTemplate.checkSecretkey(secretkey); } /** - * 构建URL:校验ticket的URL - * @param ticket ticket码 - * @param ssoLogoutCallUrl 单点注销时的回调URL - * @return 构建完毕的URL - */ - public static String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) { - return saSsoTemplate.buildCheckTicketUrl(ticket, ssoLogoutCallUrl); - } - - /** - * 为指定账号id注册单点注销回调URL + * 根据参数计算签名 * @param loginId 账号id - * @param sloCallbackUrl 单点注销时的回调URL + * @param timestamp 当前时间戳,13位 + * @param nonce 随机字符串 + * @param secretkey 账号id + * @return 签名 */ - public static void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) { - saSsoTemplate.registerSloCallbackUrl(loginId, sloCallbackUrl); + public static String getSign(Object loginId, String timestamp, String nonce, String secretkey) { + return saSsoTemplate.getSign(loginId, timestamp, nonce, secretkey); } /** - * 循环调用Client端单点注销回调 - * @param loginId 账号id - * @param fun 调用方法 + * 给 url 追加 sign 等参数 + * @param loginId + * @return 加工后的url */ - public static void forEachSloUrl(Object loginId, CallSloUrlFunction fun) { - saSsoTemplate.forEachSloUrl(loginId, fun); + public static String addSignParams(String url, Object loginId) { + return saSsoTemplate.addSignParams(url, loginId); } /** - * 构建URL:单点注销URL - * @param loginId 要注销的账号id - * @return 单点注销URL + * 校验签名 + * @param req request */ - public static String buildSloUrl(Object loginId) { - return saSsoTemplate.buildSloUrl(loginId); + public static void checkSign(SaRequest req) { + saSsoTemplate.checkSign(req); } /** - * 指定账号单点注销 - * @param secretkey 校验秘钥 - * @param loginId 指定账号 - * @param fun 调用方法 + * 校验时间戳与当前时间的差距是否超出限制 + * @param timestamp 时间戳 */ - public static void singleLogout(String secretkey, Object loginId, CallSloUrlFunction fun) { - saSsoTemplate.singleLogout(secretkey, loginId, fun); - } - - /** - * 获取:账号资料 - * @param loginId 账号id - * @return 账号资料 - */ - public static Object getUserinfo(Object loginId) { - return saSsoTemplate.getUserinfo(loginId); + public static void checkTimestamp(long timestamp) { + saSsoTemplate.checkTimestamp(timestamp); } } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoExceptionCode.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoExceptionCode.java index cfd1afba..0cf87c37 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoExceptionCode.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoExceptionCode.java @@ -25,5 +25,14 @@ public class SaSsoExceptionCode { /** 在模式三下,sso-client 调用 sso-server 端 单点注销接口 时,得到的响应是注销失败 */ public static final int CODE_20006 = 20006; + + /** http 请求调用 提供的 timestamp 与当前时间的差距超出允许的范围 */ + public static final int CODE_20007 = 20007; + + /** http 请求调用 提供的 sign 无效 */ + public static final int CODE_20008 = 20008; + + /** 本地系统没有配置 secretkey 字段 */ + public static final int CODE_20009 = 20009; } diff --git a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaBeanInject.java b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaBeanInject.java index 44d5ac53..ffd93abf 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaBeanInject.java +++ b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/SaBeanInject.java @@ -15,6 +15,7 @@ import cn.dev33.satoken.id.SaIdTemplate; import cn.dev33.satoken.id.SaIdUtil; import cn.dev33.satoken.json.SaJsonTemplate; import cn.dev33.satoken.listener.SaTokenListener; +import cn.dev33.satoken.sign.SaSignTemplate; import cn.dev33.satoken.stp.StpInterface; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpUtil; @@ -127,6 +128,16 @@ public class SaBeanInject { public void setSaJsonTemplate(SaJsonTemplate saJsonTemplate) { SaManager.setSaJsonTemplate(saJsonTemplate); } + + /** + * 注入自定义的 参数签名 Bean + * + * @param saSignTemplate 参数签名 Bean + */ + @Autowired(required = false) + public void setSaSignTemplate(SaSignTemplate saSignTemplate) { + SaManager.setSaSignTemplate(saSignTemplate); + } /** * 注入自定义的 StpLogic diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java index 12f6e765..a001bbad 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java @@ -18,7 +18,9 @@ import cn.dev33.satoken.context.second.SaTokenSecondContextCreator; import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.id.SaIdTemplate; import cn.dev33.satoken.id.SaIdUtil; +import cn.dev33.satoken.json.SaJsonTemplate; import cn.dev33.satoken.listener.SaTokenListener; +import cn.dev33.satoken.sign.SaSignTemplate; import cn.dev33.satoken.solon.integration.SaContextForSolon; import cn.dev33.satoken.solon.integration.SaTokenMethodInterceptor; import cn.dev33.satoken.stp.StpInterface; @@ -90,6 +92,16 @@ public class XPluginImp implements Plugin { SaBasicUtil.saBasicTemplate = bw.raw(); }); + // Sa-Token JSON 转换器 Bean + Aop.getAsyn(SaJsonTemplate.class, bw->{ + SaManager.setSaJsonTemplate(bw.raw()); + }); + + // Sa-Token 参数签名算法 Bean + Aop.getAsyn(SaSignTemplate.class, bw->{ + SaManager.setSaSignTemplate(bw.raw()); + }); + // 自定义 StpLogic 对象 Aop.getAsyn(StpLogic.class, bw->{ StpUtil.setStpLogic(bw.raw()); diff --git a/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java b/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java index ab2e81ea..55529555 100644 --- a/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java +++ b/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaBeanInject.java @@ -15,6 +15,7 @@ import cn.dev33.satoken.id.SaIdTemplate; import cn.dev33.satoken.id.SaIdUtil; import cn.dev33.satoken.json.SaJsonTemplate; import cn.dev33.satoken.listener.SaTokenListener; +import cn.dev33.satoken.sign.SaSignTemplate; import cn.dev33.satoken.stp.StpInterface; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpUtil; @@ -127,6 +128,16 @@ public class SaBeanInject { public void setSaJsonTemplate(SaJsonTemplate saJsonTemplate) { SaManager.setSaJsonTemplate(saJsonTemplate); } + + /** + * 注入自定义的 参数签名 Bean + * + * @param saSignTemplate 参数签名 Bean + */ + @Autowired(required = false) + public void setSaSignTemplate(SaSignTemplate saSignTemplate) { + SaManager.setSaSignTemplate(saSignTemplate); + } /** * 注入自定义的 StpLogic