diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientConfig.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientConfig.java index 89d2c12d..097adfb5 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientConfig.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientConfig.java @@ -316,7 +316,7 @@ public class SaSsoClientConfig implements Serializable { @Override public String toString() { - return "SaSsoConfig [" + return "SaSsoClientConfig [" + "mode=" + mode + ", client=" + client + ", serverUrl=" + serverUrl diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java index 21a9d3d6..a0855c57 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoServerConfig.java @@ -64,6 +64,11 @@ public class SaSsoServerConfig implements Serializable { */ public Boolean isHttp = false; + /** + * 在 Access-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 + */ + public int maxRegClient = 32; + /** * 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true) */ @@ -154,6 +159,22 @@ public class SaSsoServerConfig implements Serializable { return this; } + /** + * @return maxLoginClient 在 Access-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 + */ + public int getMaxRegClient() { + return maxRegClient; + } + + /** + * @param maxRegClient 在 Access-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 + * @return 对象自身 + */ + public SaSsoServerConfig setMaxRegClient(int maxRegClient) { + this.maxRegClient = maxRegClient; + return this; + } + /** * 获取 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true) * @@ -191,6 +212,7 @@ public class SaSsoServerConfig implements Serializable { + ", allowUrl=" + allowUrl + ", isSlo=" + isSlo + ", isHttp=" + isHttp + + ", maxRegClient=" + maxRegClient + ", isCheckSign=" + isCheckSign + "]"; } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaSsoClientModel.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaSsoClientModel.java index ae50feb1..db3ed6a4 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaSsoClientModel.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaSsoClientModel.java @@ -24,10 +24,20 @@ package cn.dev33.satoken.sso.model; */ public class SaSsoClientModel { + /** + * 此 client 登录模式(1=模式一,2=模式二,3=模式三) + */ + public int mode; + /** * 客户端标识 */ - public String client; + public String client; + + /** + * 此次登录 token 值 + */ + public String tokenValue; /** * 单点注销回调url @@ -37,7 +47,12 @@ public class SaSsoClientModel { /** * 此 client 注册信息的时间,13位时间戳 */ - public Long regTime; + public long regTime; + + /** + * 此账号有记录以来为第几次登录,默认从0开始递增 + */ + public int index; public SaSsoClientModel() { } @@ -48,6 +63,27 @@ public class SaSsoClientModel { this.regTime = System.currentTimeMillis(); } + + /** + * 获取 此 client 登录模式(1=模式一,2=模式二,3=模式三) + * + * @return mode 此 client 登录模式(1=模式一,2=模式二,3=模式三) + */ + public int getMode() { + return this.mode; + } + + /** + * 设置 此 client 登录模式(1=模式一,2=模式二,3=模式三) + * + * @param mode 此 client 登录模式(1=模式一,2=模式二,3=模式三) + * @return / + */ + public SaSsoClientModel setMode(int mode) { + this.mode = mode; + return this; + } + /** * 获取 客户端标识 * @@ -61,9 +97,31 @@ public class SaSsoClientModel { * 设置 客户端标识 * * @param client 客户端标识 + * @return / */ - public void setClient(String client) { + public SaSsoClientModel setClient(String client) { this.client = client; + return this; + } + + /** + * 获取 此次登录 token 值 + * + * @return tokenValue 此次登录 token 值 + */ + public String getTokenValue() { + return this.tokenValue; + } + + /** + * 设置 此次登录 token 值 + * + * @param tokenValue 此次登录 token 值 + * @return / + */ + public SaSsoClientModel setTokenValue(String tokenValue) { + this.tokenValue = tokenValue; + return this; } /** @@ -79,9 +137,11 @@ public class SaSsoClientModel { * 设置 单点注销回调url * * @param ssoLogoutCall 单点注销回调url + * @return / */ - public void setSsoLogoutCall(String ssoLogoutCall) { + public SaSsoClientModel setSsoLogoutCall(String ssoLogoutCall) { this.ssoLogoutCall = ssoLogoutCall; + return this; } /** @@ -89,7 +149,7 @@ public class SaSsoClientModel { * * @return regTime 此 client 注册信息的时间,13位时间戳 */ - public Long getRegTime() { + public long getRegTime() { return this.regTime; } @@ -97,17 +157,42 @@ public class SaSsoClientModel { * 设置 此 client 注册信息的时间,13位时间戳 * * @param regTime 此 client 注册信息的时间,13位时间戳 + * @return / */ - public void setRegTime(Long regTime) { + public SaSsoClientModel setRegTime(long regTime) { this.regTime = regTime; + return this; + } + + /** + * 获取 此账号有记录以来为第几次登录,默认从0开始递增 + * + * @return regTime 此账号有记录以来为第几次登录,默认从0开始递增 + */ + public long getIndex() { + return this.index; + } + + /** + * 设置 此账号有记录以来为第几次登录,默认从0开始递增 + * + * @param index 此账号有记录以来为第几次登录,默认从0开始递增 + * @return / + */ + public SaSsoClientModel setIndex(int index) { + this.index = index; + return this; } @Override public String toString() { return "SaSsoClientModel{" + - "client='" + client + '\'' + + "mode=" + mode + + ", client='" + client + '\'' + + ", tokenValue='" + tokenValue + '\'' + ", ssoLogoutCall='" + ssoLogoutCall + '\'' + - ", regTime='" + regTime + '\'' + + ", regTime=" + regTime + + ", index=" + index + '}'; } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ParamName.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ParamName.java index 92d33323..ab456a06 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ParamName.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ParamName.java @@ -47,6 +47,9 @@ public class ParamName { /** Client端单点注销时-回调URL 参数名称 */ public String ssoLogoutCall = "ssoLogoutCall"; + /** 是否为超过 maxRegClient 的自动注销 */ + public String autoLogout = "autoLogout"; + public String name = "name"; public String pwd = "pwd"; diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java index b6dcc876..d9d922fc 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java @@ -283,13 +283,25 @@ public class SaSsoServerTemplate extends SaSsoTemplate { SaSession session = getStpLogic().getSessionByLoginId(loginId); - // 取 + // 取出原来的 List scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new); - // 加 - scmList.add(new SaSsoClientModel(client, sloCallbackUrl)); + // 将 新登录client 加入到集合中 + SaSsoClientModel scm = new SaSsoClientModel(); + scm.mode = 3; + scm.client = client; + scm.ssoLogoutCall = sloCallbackUrl; + scm.regTime = System.currentTimeMillis(); + scm.index = calcNextIndex(scmList); + scmList.add(scm); - // 存 + // 如果登录的client数量超过了限制,则将最早的一个登录进行清退 + if(scmList.size() > getServerConfig().getMaxRegClient()) { + SaSsoClientModel removeScm = scmList.remove(0); + notifyClientLogout(loginId, removeScm, true); + } + + // 存入持久库 session.set(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, scmList); } @@ -305,31 +317,85 @@ public class SaSsoServerTemplate extends SaSsoTemplate { return; } - SaSsoServerConfig ssoConfig = getServerConfig(); - // step.1 遍历通知 Client 端注销会话 List scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new); scmList.forEach(scm -> { - // url - String sloCallUrl = scm.getSsoLogoutCall(); - - // 参数 - Map paramsMap = new TreeMap<>(); - paramsMap.put(paramName.client, scm.getClient()); - paramsMap.put(paramName.loginId, loginId); - String signParamsStr = getSignTemplate(scm.getClient()).addSignParamsAndJoin(paramsMap); - - // 拼接 - String finalUrl = SaFoxUtil.joinParam(sloCallUrl, signParamsStr); - - // 发起请求 - ssoConfig.sendHttp.apply(finalUrl); + notifyClientLogout(loginId, scm, false); }); // step.2 Server端注销 getStpLogic().logout(loginId); } + /** + * 通知指定账号的指定客户端注销 + * @param loginId 指定账号 + * @param scm 客户端信息对象 + * @param autoLogout 是否为超过 maxRegClient 的自动注销 + */ + public void notifyClientLogout(Object loginId, SaSsoClientModel scm, boolean autoLogout) { + + // 如果给个null值,不进行任何操作 + if(scm == null) { + return; + } + + // 如果是模式二登录的 + if(scm.mode == SaSsoConsts.SSO_MODE_2) { + // 获取登录 token + String tokenValue = scm.tokenValue; + if(SaFoxUtil.isEmpty(tokenValue)) { + return; + } + + // 注销此 token + getStpLogic().logoutByTokenValue(scm.tokenValue); + } + + // 如果是模式三登录的 + else if(scm.mode != SaSsoConsts.SSO_MODE_3) { + // url + String sloCallUrl = scm.getSsoLogoutCall(); + if(SaFoxUtil.isEmpty(sloCallUrl)) { + return; + } + + // 参数 + Map paramsMap = new TreeMap<>(); + paramsMap.put(paramName.client, scm.getClient()); + paramsMap.put(paramName.loginId, loginId); + paramsMap.put(paramName.autoLogout, autoLogout); + String signParamsStr = getSignTemplate(scm.getClient()).addSignParamsAndJoin(paramsMap); + + // 拼接 + String finalUrl = SaFoxUtil.joinParam(sloCallUrl, signParamsStr); + + // 发起请求 + getServerConfig().sendHttp.apply(finalUrl); + } + } + + /** + * 计算下一个 index 值 + * @param scmList / + * @return / + */ + private int calcNextIndex(List scmList) { + // 如果目前还没有任何登录记录,则直接返回0 + if(scmList == null || scmList.isEmpty()) { + return 0; + } + // 获取目前最大的index值 + int maxIndex = scmList.get(scmList.size() - 1).index; + + // 如果已经是 int 最大值了,则直接返回0 + if(maxIndex == Integer.MAX_VALUE) { + return 0; + } + + // 否则返回最大值+1 + return maxIndex++; + } // ---------------------- 构建URL ---------------------- diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/util/SaSsoConsts.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/util/SaSsoConsts.java index 6c2654fb..64050ae7 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/util/SaSsoConsts.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/util/SaSsoConsts.java @@ -45,4 +45,11 @@ public class SaSsoConsts { /** 表示请求没有得到任何有效处理 {msg: "not handle"} */ public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}"; + /** SSO 模式1 */ + public static final int SSO_MODE_1 = 1; + /** SSO 模式2 */ + public static final int SSO_MODE_2 = 2; + /** SSO 模式3 */ + public static final int SSO_MODE_3 = 3; + } diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/sso/SaSsoAutoConfigure.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/sso/SaSsoAutoConfigure.java index c09d5b3b..4b40bada 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/sso/SaSsoAutoConfigure.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/sso/SaSsoAutoConfigure.java @@ -15,11 +15,13 @@ */ package cn.dev33.satoken.solon.sso; -import cn.dev33.satoken.config.SaSsoConfig; import cn.dev33.satoken.sso.SaSsoManager; -import cn.dev33.satoken.sso.SaSsoProcessor; -import cn.dev33.satoken.sso.template.SaSsoTemplate; -import cn.dev33.satoken.sso.template.SaSsoUtil; +import cn.dev33.satoken.sso.config.SaSsoClientConfig; +import cn.dev33.satoken.sso.config.SaSsoServerConfig; +import cn.dev33.satoken.sso.processor.SaSsoClientProcessor; +import cn.dev33.satoken.sso.processor.SaSsoServerProcessor; +import cn.dev33.satoken.sso.template.SaSsoClientTemplate; +import cn.dev33.satoken.sso.template.SaSsoServerTemplate; import org.noear.solon.annotation.Bean; import org.noear.solon.annotation.Condition; import org.noear.solon.annotation.Configuration; @@ -40,21 +42,33 @@ public class SaSsoAutoConfigure implements InitializingBean { @Override public void afterInjection() throws Throwable { - appContext.subBeansOfType(SaSsoTemplate.class, bean->{ - SaSsoUtil.ssoTemplate = bean; - SaSsoProcessor.instance.ssoTemplate = bean; + appContext.subBeansOfType(SaSsoServerTemplate.class, bean->{ + SaSsoServerProcessor.instance.ssoServerTemplate = bean; + }); + appContext.subBeansOfType(SaSsoClientTemplate.class, bean->{ + SaSsoClientProcessor.instance.ssoClientTemplate = bean; }); - appContext.subBeansOfType(SaSsoConfig.class, bean->{ - SaSsoManager.setConfig(bean); + appContext.subBeansOfType(SaSsoServerConfig.class, bean->{ + SaSsoManager.setServerConfig(bean); + }); + appContext.subBeansOfType(SaSsoClientConfig.class, bean->{ + SaSsoManager.setClientConfig(bean); }); } /** - * 获取 SSO 配置Bean + * 获取 SSO Server 配置Bean * */ @Bean - public SaSsoConfig getConfig(@Inject(value = "${sa-token.sso}",required = false) SaSsoConfig ssoConfig) { + public SaSsoServerConfig getConfig(@Inject(value = "${sa-token.sso-server}",required = false) SaSsoServerConfig ssoConfig) { + return ssoConfig; + } + /** + * 获取 SSO Client 配置Bean + * */ + @Bean + public SaSsoClientConfig getClientConfig(@Inject(value = "${sa-token.sso-client}",required = false) SaSsoClientConfig ssoConfig) { return ssoConfig; } } \ No newline at end of file