diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/h5/H5Controller.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/h5/H5Controller.java index 944b20ca..ebabaa76 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/h5/H5Controller.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/src/main/java/com/pj/h5/H5Controller.java @@ -34,7 +34,7 @@ public class H5Controller { return SaResult.data(redirect); } else { // 模式二或模式三 - String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), client, redirect); + String redirectUrl = SaSsoUtil.buildRedirectUrl(client, redirect, StpUtil.getLoginId(), StpUtil.getLoginDeviceId()); return SaResult.data(redirectUrl); } } 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 5986a9a2..47897c8c 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 @@ -5,6 +5,8 @@ import cn.dev33.satoken.sign.SaSignUtil; import cn.dev33.satoken.sso.config.SaSsoServerConfig; import cn.dev33.satoken.sso.processor.SaSsoServerProcessor; import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.stp.parameter.SaLoginParameter; +import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; @@ -44,7 +46,8 @@ public class SsoServerController { ssoServer.doLoginHandle = (name, pwd) -> { // 此处仅做模拟登录,真实环境应该查询数据进行登录 if("sa".equals(name) && "123456".equals(pwd)) { - StpUtil.login(10001); + String deviceId = SaHolder.getRequest().getParam("deviceId", SaFoxUtil.getRandomString(32)); + StpUtil.login(10001, SaLoginParameter.create().setDeviceId(deviceId)); return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue()); } return SaResult.error("登录失败!"); diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/client/SaSsoMessageLogoutCallHandle.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/client/SaSsoMessageLogoutCallHandle.java index f728f4af..9da82dc6 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/client/SaSsoMessageLogoutCallHandle.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/client/SaSsoMessageLogoutCallHandle.java @@ -25,6 +25,7 @@ import cn.dev33.satoken.sso.template.SaSsoClientTemplate; import cn.dev33.satoken.sso.template.SaSsoTemplate; import cn.dev33.satoken.sso.util.SaSsoConsts; import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.stp.parameter.SaLogoutParameter; import cn.dev33.satoken.util.SaResult; /** @@ -64,9 +65,12 @@ public class SaSsoMessageLogoutCallHandle implements SaSsoMessageHandle { // 获取参数 String loginId = req.getParamNotNull(paramName.loginId); + String deviceId = message.getString(paramName.deviceId); // 注销当前应用端会话 - stpLogic.logout(loginId); + stpLogic.logout(loginId, new SaLogoutParameter() + .setDeviceId(deviceId) + ); // 响应 return SaResult.ok("单点注销回调成功"); diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/server/SaSsoMessageCheckTicketHandle.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/server/SaSsoMessageCheckTicketHandle.java index 22ae41c0..1a106263 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/server/SaSsoMessageCheckTicketHandle.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/server/SaSsoMessageCheckTicketHandle.java @@ -18,15 +18,15 @@ package cn.dev33.satoken.sso.message.handle.server; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.model.SaRequest; -import cn.dev33.satoken.sso.SaSsoManager; import cn.dev33.satoken.sso.config.SaSsoServerConfig; import cn.dev33.satoken.sso.message.SaSsoMessage; import cn.dev33.satoken.sso.message.handle.SaSsoMessageHandle; +import cn.dev33.satoken.sso.model.TicketModel; import cn.dev33.satoken.sso.name.ParamName; import cn.dev33.satoken.sso.template.SaSsoServerTemplate; import cn.dev33.satoken.sso.template.SaSsoTemplate; import cn.dev33.satoken.sso.util.SaSsoConsts; -import cn.dev33.satoken.util.SaFoxUtil; +import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.util.SaResult; /** @@ -60,10 +60,12 @@ public class SaSsoMessageCheckTicketHandle implements SaSsoMessageHandle { // 1、获取参数 SaRequest req = SaHolder.getRequest(); SaSsoServerConfig ssoServerConfig = ssoServerTemplate.getServerConfig(); + StpLogic stpLogic = ssoServerTemplate.getStpLogic(); String client = req.getParam(paramName.client); String ticket = req.getParamNotNull(paramName.ticket); String sloCallback = req.getParam(paramName.ssoLogoutCall); + // 2、校验提供的client是否为非法字符 if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) { return SaResult.error("无效 client 标识:" + client); @@ -77,17 +79,21 @@ public class SaSsoMessageCheckTicketHandle implements SaSsoMessageHandle { // } // 4、校验ticket,获取 loginId - Object loginId = ssoServerTemplate.checkTicket(ticket, client); - if(SaFoxUtil.isEmpty(loginId)) { - return SaResult.error("无效ticket:" + ticket); - } + TicketModel ticketModel = ssoServerTemplate.checkTicketParamAndDelete(ticket, client); + Object loginId = ticketModel.getLoginId(); // 5、注册此客户端的单点注销回调URL ssoServerTemplate.registerSloCallbackUrl(loginId, client, sloCallback); // 6、给 client 端响应结果 - long remainSessionTimeout = ssoServerTemplate.getStpLogic().getSessionTimeoutByLoginId(loginId); - SaResult result = SaResult.data(loginId).set(paramName.remainSessionTimeout, remainSessionTimeout); + SaResult result = SaResult.ok(); + result.setData(loginId); // 兼容历史版本 + result.set(paramName.loginId, loginId); + result.set(paramName.tokenValue, ticketModel.getTokenValue()); + result.set(paramName.deviceId, stpLogic.getLoginDeviceIdByToken(ticketModel.getTokenValue())); + result.set(paramName.remainTokenTimeout, stpLogic.getTokenTimeout(ticketModel.getTokenValue())); + result.set(paramName.remainSessionTimeout, stpLogic.getSessionTimeoutByLoginId(loginId)); + result = ssoServerConfig.checkTicketAppendData.apply(loginId, result); return result; } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/server/SaSsoMessageSignoutHandle.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/server/SaSsoMessageSignoutHandle.java index 43b83d57..728b007f 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/server/SaSsoMessageSignoutHandle.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/server/SaSsoMessageSignoutHandle.java @@ -24,6 +24,7 @@ import cn.dev33.satoken.sso.name.ParamName; import cn.dev33.satoken.sso.template.SaSsoServerTemplate; import cn.dev33.satoken.sso.template.SaSsoTemplate; import cn.dev33.satoken.sso.util.SaSsoConsts; +import cn.dev33.satoken.stp.parameter.SaLogoutParameter; import cn.dev33.satoken.util.SaResult; /** @@ -61,9 +62,12 @@ public class SaSsoMessageSignoutHandle implements SaSsoMessageHandle { // 获取参数 SaRequest req = SaHolder.getRequest(); String loginId = req.getParam(paramName.loginId); + String deviceId = req.getParam(paramName.deviceId); // step.2 单点注销 - ssoServerTemplate.ssoLogout(loginId); + SaLogoutParameter logoutParameter = ssoServerTemplate.getStpLogic().createSaLogoutParameter() + .setDeviceId(deviceId); + ssoServerTemplate.ssoLogout(loginId, logoutParameter); // 响应 return SaResult.ok(); diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaCheckTicketResult.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaCheckTicketResult.java index d859bd79..68cf569f 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaCheckTicketResult.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/SaCheckTicketResult.java @@ -32,21 +32,31 @@ public class SaCheckTicketResult implements Serializable { /** 账号id */ public Object loginId; + /** 在 sso-server 端的 token 值 */ + public String tokenValue; + + /** 登录设备 id */ + public String deviceId; + + /** 此账号 token 剩余有效期 */ + public Long remainTokenTimeout; + /** 此账号会话剩余有效期 */ - public long remainSessionTimeout; + public Long remainSessionTimeout; /** 从 sso-server 返回的所有参数 */ public SaResult result; - public SaCheckTicketResult(Object loginId, long remainSessionTimeout, SaResult result) { - this.loginId = loginId; - this.remainSessionTimeout = remainSessionTimeout; - this.result = result; + public SaCheckTicketResult() { } + @Override public String toString() { - return "CheckTicketResult{" + + return "SaCheckTicketResult{" + "loginId=" + loginId + + ", tokenValue='" + tokenValue + '\'' + + ", deviceId='" + deviceId + '\'' + + ", remainTokenTimeout=" + remainTokenTimeout + ", remainSessionTimeout=" + remainSessionTimeout + ", result=" + result + '}'; diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/TicketModel.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/TicketModel.java index 5f1191bb..c766e6f4 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/TicketModel.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/model/TicketModel.java @@ -37,16 +37,21 @@ public class TicketModel implements Serializable { */ public String client; - /** - * 设备 id - */ - public String deviceId; +// /** +// * 设备 id +// */ +// public String deviceId; /** * 对应 loginId */ public Object loginId; + /** + * 会话 token + */ + public String tokenValue; + /** * 创建时间,13位时间戳 */ @@ -64,14 +69,14 @@ public class TicketModel implements Serializable { * @param ticket 授权码 * @param client 应用id * @param loginId 对应的账号id - * @param deviceId 重定向地址 + * @param tokenValue 会话 token */ - public TicketModel(String ticket, String client, String deviceId, Object loginId) { + public TicketModel(String ticket, String client, Object loginId, String tokenValue) { this(); this.ticket = ticket; this.client = client; - this.deviceId = deviceId; this.loginId = loginId; + this.tokenValue = tokenValue; } @@ -117,26 +122,6 @@ public class TicketModel implements Serializable { return this; } - /** - * 获取 设备 id - * - * @return / - */ - public String getDeviceId() { - return this.deviceId; - } - - /** - * 设置 设备 id - * - * @param deviceId / - * @return 对象自身 - */ - public TicketModel setDeviceId(String deviceId) { - this.deviceId = deviceId; - return this; - } - /** * 获取 对应 loginId * @@ -157,6 +142,26 @@ public class TicketModel implements Serializable { return this; } + /** + * 获取 会话 token + * + * @return tokenValue 会话 token + */ + public String getTokenValue() { + return this.tokenValue; + } + + /** + * 设置 会话 token + * + * @param tokenValue 会话 token + * @return 对象自身 + */ + public TicketModel setTokenValue(String tokenValue) { + this.tokenValue = tokenValue; + return this; + } + /** * 获取 创建时间,13位时间戳 * @@ -182,8 +187,8 @@ public class TicketModel implements Serializable { return "TicketModel{" + "ticket='" + ticket + '\'' + ", client='" + client + '\'' + - ", deviceId='" + deviceId + '\'' + ", loginId=" + loginId + + ", tokenValue=" + tokenValue + ", createTime=" + createTime + '}'; } 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 c0f7b188..86da564e 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 @@ -41,6 +41,12 @@ public class ParamName { /** client参数名称 */ public String client = "client"; + /** tokenValue 参数 */ + public String tokenValue = "tokenValue"; + + /** deviceId 参数名称 */ + public String deviceId = "deviceId"; + /** secretkey参数名称 */ public String secretkey = "secretkey"; @@ -60,4 +66,10 @@ public class ParamName { /** Session 剩余有效期 参数名称 */ public String remainSessionTimeout = "remainSessionTimeout"; + /** token 剩余有效期 参数名称 */ + public String remainTokenTimeout = "remainTokenTimeout"; + + /** singleDeviceIdLogout 参数 */ + public String singleDeviceIdLogout = "singleDeviceIdLogout"; + } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java index aa78a3ab..58ae428d 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoClientProcessor.java @@ -24,11 +24,14 @@ import cn.dev33.satoken.sso.error.SaSsoErrorCode; import cn.dev33.satoken.sso.exception.SaSsoException; import cn.dev33.satoken.sso.message.SaSsoMessage; import cn.dev33.satoken.sso.model.SaCheckTicketResult; +import cn.dev33.satoken.sso.model.TicketModel; import cn.dev33.satoken.sso.name.ApiName; import cn.dev33.satoken.sso.name.ParamName; import cn.dev33.satoken.sso.template.SaSsoClientTemplate; import cn.dev33.satoken.sso.util.SaSsoConsts; import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.stp.parameter.SaLoginParameter; +import cn.dev33.satoken.stp.parameter.SaLogoutParameter; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; @@ -139,7 +142,10 @@ public class SaSsoClientProcessor { } // 3、登录并重定向至back地址 - stpLogic.login(ctr.loginId, ctr.remainSessionTimeout); + stpLogic.login(ctr.loginId, new SaLoginParameter() + .setTimeout(ctr.remainTokenTimeout) + .setDeviceId(ctr.deviceId) + ); return res.redirect(back); } } @@ -211,6 +217,7 @@ public class SaSsoClientProcessor { SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); StpLogic stpLogic = ssoClientTemplate.getStpLogic(); + boolean singleDeviceIdLogout = req.isParam(ssoClientTemplate.paramName.singleDeviceIdLogout, "true"); // 如果未登录,则无需注销 if( ! stpLogic.isLogin()) { @@ -218,7 +225,11 @@ public class SaSsoClientProcessor { } // 调用 sso-server 认证中心单点注销API - SaSsoMessage message = ssoClientTemplate.buildSloMessage(stpLogic.getLoginId()); + SaLogoutParameter logoutParameter = stpLogic.createSaLogoutParameter(); + if(singleDeviceIdLogout) { + logoutParameter.setDeviceId(stpLogic.getLoginDeviceId()); + } + SaSsoMessage message = ssoClientTemplate.buildSloMessage(stpLogic.getLoginId(), logoutParameter); SaResult result = ssoClientTemplate.pushMessageAsSaResult(message); // 校验响应状态码 @@ -248,18 +259,20 @@ public class SaSsoClientProcessor { // 获取参数 String loginId = req.getParamNotNull(paramName.loginId); + String deviceId = req.getParam(paramName.deviceId); // String client = req.getParam(paramName.client); // String autoLogout = req.getParam(paramName.autoLogout); // 校验参数签名 if(ssoConfig.getIsCheckSign()) { - ssoClientTemplate.getSignTemplate(ssoConfig.getClient()).checkRequest(req, paramName.loginId, paramName.client, paramName.autoLogout); + ssoClientTemplate.getSignTemplate(ssoConfig.getClient()).checkRequest(req); } else { SaSsoManager.printNoCheckSignWarningByRuntime(); } // 注销当前应用端会话 - stpLogic.logout(loginId); + SaLogoutParameter logoutParameter = ssoClientTemplate.getStpLogic().createSaLogoutParameter(); + stpLogic.logout(loginId, logoutParameter.setDeviceId(deviceId)); // 响应 return SaResult.ok("单点注销回调成功"); @@ -269,9 +282,10 @@ public class SaSsoClientProcessor { /** * 封装:校验ticket,取出loginId,如果 ticket 无效则抛出异常 (适用于模式二或模式三) + * * @param ticket ticket码 * @param currUri 当前路由的uri,用于计算单点注销回调地址 (如果是使用模式二,可以填写null) - * @return loginId + * @return SaCheckTicketResult */ public SaCheckTicketResult checkTicket(String ticket, String currUri) { SaSsoClientConfig cfg = ssoClientTemplate.getClientConfig(); @@ -304,18 +318,16 @@ public class SaSsoClientProcessor { // 校验 if(result.getCode() != null && result.getCode() == SaResult.CODE_SUCCESS) { - // 取出 loginId - Object loginId = result.getData(); - if(SaFoxUtil.isEmpty(loginId)) { - throw new SaSsoException("无效ticket:" + ticket).setCode(SaSsoErrorCode.CODE_30004); - } - // 取出 Session 剩余有效期 - Long remainSessionTimeout = result.get(paramName.remainSessionTimeout, Long.class); - if(remainSessionTimeout == null) { - remainSessionTimeout = ssoClientTemplate.getStpLogic().getConfigOrGlobal().getTimeout(); - } - // 构建返回 - return new SaCheckTicketResult(loginId, remainSessionTimeout, result); + + SaCheckTicketResult ctr = new SaCheckTicketResult(); + ctr.loginId = result.get(paramName.loginId); + ctr.tokenValue = result.get(paramName.tokenValue, String.class); + ctr.deviceId = result.get(paramName.deviceId, String.class); + ctr.remainTokenTimeout = result.get(paramName.remainTokenTimeout, Long.class); + ctr.remainSessionTimeout = result.get(paramName.remainSessionTimeout, Long.class); + ctr.result = result; + + return ctr; } else { // 将 sso-server 回应的消息作为异常抛出 throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30005); @@ -328,15 +340,18 @@ public class SaSsoClientProcessor { // 可能会导致调用失败(注意是可能,而非一定), // 解决方案为:在当前 sso-client 端也按照 sso-server 端的格式重写 SaSsoClientProcessor 里的方法 - // 取出 loginId - Object loginId = SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, cfg.getClient()); - if(SaFoxUtil.isEmpty(loginId)) { - throw new SaSsoException("无效ticket:" + ticket).setCode(SaSsoErrorCode.CODE_30004); - } - // 取出 Session 剩余有效期 - long remainSessionTimeout = ssoClientTemplate.getStpLogic().getSessionTimeoutByLoginId(loginId); - // 构建返回 - return new SaCheckTicketResult(loginId, remainSessionTimeout, null); + StpLogic stpLogic = ssoClientTemplate.getStpLogic(); + TicketModel ticketModel = SaSsoServerProcessor.instance.ssoServerTemplate.checkTicketParamAndDelete(ticket, cfg.getClient()); + + SaCheckTicketResult ctr = new SaCheckTicketResult(); + ctr.loginId = ticketModel.getLoginId(); + ctr.tokenValue = ticketModel.getTokenValue(); + ctr.deviceId = stpLogic.getLoginDeviceIdByToken(ticketModel.getTokenValue()); + ctr.remainTokenTimeout = stpLogic.getTokenTimeout(ticketModel.getTokenValue()); + ctr.remainSessionTimeout = stpLogic.getSessionTimeoutByLoginId(ticketModel.getLoginId()); + ctr.result = null; + + return ctr; } } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java index f8e97c22..ee440091 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/processor/SaSsoServerProcessor.java @@ -28,6 +28,7 @@ import cn.dev33.satoken.sso.name.ParamName; import cn.dev33.satoken.sso.template.SaSsoServerTemplate; import cn.dev33.satoken.sso.util.SaSsoConsts; import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.stp.parameter.SaLogoutParameter; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; @@ -141,7 +142,7 @@ public class SaSsoServerProcessor { } // 构建并跳转 - String redirectUrl = ssoServerTemplate.buildRedirectUrl(stpLogic.getLoginId(), client, redirect); + String redirectUrl = ssoServerTemplate.buildRedirectUrl(client, redirect, stpLogic.getLoginId(), stpLogic.getTokenValue()); // 构建成功,说明 redirect 地址合法,此时需要更新一下该账号的Session有效期 if(cfg.getAutoRenewTimeout()) { stpLogic.renewTimeout(stpLogic.getConfigOrGlobal().getTimeout()); @@ -174,10 +175,15 @@ public class SaSsoServerProcessor { SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); Object loginId = ssoServerTemplate.getStpLogic().getLoginIdDefaultNull(); + boolean singleDeviceIdLogout = req.isParam(ssoServerTemplate.paramName.singleDeviceIdLogout, "true"); // 单点注销 if(SaFoxUtil.isNotEmpty(loginId)) { - ssoServerTemplate.ssoLogout(loginId); + SaLogoutParameter logoutParameter = ssoServerTemplate.getStpLogic().createSaLogoutParameter(); + if(singleDeviceIdLogout) { + logoutParameter.setDeviceId(ssoServerTemplate.getStpLogic().getLoginDeviceId()); + } + ssoServerTemplate.ssoLogout(loginId, logoutParameter); } // 完成 diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoClientTemplate.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoClientTemplate.java index be87ade0..ed069bb9 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoClientTemplate.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoClientTemplate.java @@ -25,6 +25,7 @@ import cn.dev33.satoken.sso.exception.SaSsoException; import cn.dev33.satoken.sso.message.SaSsoMessage; import cn.dev33.satoken.sso.message.handle.client.SaSsoMessageLogoutCallHandle; import cn.dev33.satoken.sso.util.SaSsoConsts; +import cn.dev33.satoken.stp.parameter.SaLogoutParameter; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; @@ -160,15 +161,18 @@ public class SaSsoClientTemplate extends SaSsoTemplate { /** * 构建消息:单点注销 + * * @param loginId 要注销的账号 id + * @param logoutParameter 单点注销 * @return 单点注销URL */ - public SaSsoMessage buildSloMessage(Object loginId) { + public SaSsoMessage buildSloMessage(Object loginId, SaLogoutParameter logoutParameter) { SaSsoClientConfig ssoConfig = getClientConfig(); SaSsoMessage message = new SaSsoMessage(); message.setType(SaSsoConsts.MESSAGE_SIGNOUT); message.set(paramName.client, ssoConfig.getClient()); message.set(paramName.loginId, loginId); + message.set(paramName.deviceId, logoutParameter.getDeviceId()); return message; } 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 b9ca8b0c..20479c7e 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 @@ -30,6 +30,7 @@ import cn.dev33.satoken.sso.message.handle.server.SaSsoMessageSignoutHandle; import cn.dev33.satoken.sso.model.SaSsoClientInfo; import cn.dev33.satoken.sso.model.TicketModel; import cn.dev33.satoken.sso.util.SaSsoConsts; +import cn.dev33.satoken.stp.parameter.SaLogoutParameter; import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; @@ -67,7 +68,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate { * @param ticket ticket码 * @param loginId 账号id */ - public void saveTicketIndex(String client, String ticket, Object loginId) { + public void saveTicketIndex(String client, Object loginId, String ticket) { long ticketTimeout = getServerConfig().getTicketTimeout(); SaManager.getSaTokenDao().set(splicingTicketIndexKey(client, loginId), String.valueOf(ticket), ticketTimeout); } @@ -186,56 +187,65 @@ public class SaSsoServerTemplate extends SaSsoTemplate { // /** - * 根据 账号id 创建一个 Ticket码 - * @param loginId 账号id + * 根据参数创建一个 ticket 码 + * * @param client 客户端标识 + * @param loginId 账号 id + * @param tokenValue 会话 Token * @return Ticket码 */ - public String createTicket(Object loginId, String client) { + public String createTicket(String client, Object loginId, String tokenValue) { // 创建 Ticket String ticket = randomTicket(loginId); TicketModel ticketModel = new TicketModel(); ticketModel.setTicket(ticket); ticketModel.setClient(client); ticketModel.setLoginId(loginId); - // TODO ticketModel.setDeviceId(); + ticketModel.setTokenValue(tokenValue); // 保存 Ticket saveTicket(ticketModel); - saveTicketIndex(client, ticket, loginId); + saveTicketIndex(client, loginId, ticket); // 返回 Ticket return ticket; } /** - * 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除 + * 校验 Ticket,无效 ticket 会抛出异常 + * * @param ticket Ticket码 - * @return 账号id + * @return / */ - public Object checkTicket(String ticket) { - return checkTicket(ticket, SaSsoConsts.CLIENT_WILDCARD); + public TicketModel checkTicket(String ticket) { + TicketModel ticketModel = getTicket(ticket); + if(ticketModel == null) { + throw new SaSsoException("无效 ticket : " + ticket).setCode(SaSsoErrorCode.CODE_30004); + } + return ticketModel; } /** - * 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除 + * 校验 Ticket 码,无效 ticket 会抛出异常,如果此ticket是有效的,则立即删除 * @param ticket Ticket码 - * @param client client 标识 * @return 账号id */ - public Object checkTicket(String ticket, String client) { - // 读取 loginId - TicketModel ticketModel = getTicket(ticket); - if(ticketModel == null) { - return null; - } + public TicketModel checkTicketParamAndDelete(String ticket) { + return checkTicketParamAndDelete(ticket, SaSsoConsts.CLIENT_WILDCARD); + } - Object loginId = ticketModel.getLoginId(); - String ticketClient = ticketModel.getClient(); - - // 解析出这个 ticket 关联的 Client + /** + * 校验 Ticket,无效 ticket 会抛出异常,如果此ticket是有效的,则立即删除 + * + * @param ticket Ticket码 + * @param client client 标识 + * @return / + */ + public TicketModel checkTicketParamAndDelete(String ticket, String client) { + TicketModel ticketModel = checkTicket(ticket); // 校验 client 参数是否正确,即:创建 ticket 的 client 和当前校验 ticket 的 client 是否一致 + String ticketClient = ticketModel.getClient(); if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) { // 如果提供的是通配符,直接越过 client 校验 } else if (SaFoxUtil.isEmpty(client) && SaFoxUtil.isEmpty(ticketClient)) { @@ -249,10 +259,10 @@ public class SaSsoServerTemplate extends SaSsoTemplate { // 删除 ticket 信息,使其只有一次性有效 deleteTicket(ticket); - deleteTicketIndex(ticket, loginId); + deleteTicketIndex(client, ticketModel.getLoginId()); // - return loginId; + return ticketModel; } /** @@ -346,13 +356,15 @@ public class SaSsoServerTemplate extends SaSsoTemplate { // ------------------- 重定向 URL 构建与校验 ------------------- /** - * 构建URL:Server端向Client下放ticket的地址 - * @param loginId 账号id + * 构建 URL:sso-server 端向 sso-client 下放 ticket 的地址 + * * @param client 客户端标识 - * @param redirect Client端提供的重定向地址 - * @return see note + * @param redirect sso-client 端的重定向地址 + * @param loginId 账号 id + * @param tokenValue 会话 token + * @return / */ - public String buildRedirectUrl(Object loginId, String client, String redirect) { + public String buildRedirectUrl(String client, String redirect, Object loginId, String tokenValue) { // 校验 重定向地址 是否合法 checkRedirectUrl(client, redirect); @@ -361,7 +373,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate { deleteTicket(getTicketValue(client, loginId)); // 创建 新Ticket - String ticket = createTicket(loginId, client); + String ticket = createTicket(client, loginId, tokenValue); // 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket) return SaFoxUtil.joinParam(encodeBackParam(redirect), paramName.ticket, ticket); @@ -521,7 +533,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate { for (;;) { if(scmList.size() > maxRegClient) { SaSsoClientInfo removeScm = scmList.remove(0); - notifyClientLogout(loginId, removeScm, true); + notifyClientLogout(loginId, null, removeScm, true); } else { break; } @@ -557,12 +569,24 @@ public class SaSsoServerTemplate extends SaSsoTemplate { /** * 指定账号单点注销 + * * @param loginId 指定账号 */ public void ssoLogout(Object loginId) { + ssoLogout(loginId, getStpLogic().createSaLogoutParameter()); + } + + /** + * 指定账号单点注销 + * + * @param loginId 指定账号 + * @param logoutParameter 注销参数 + */ + public void ssoLogout(Object loginId, SaLogoutParameter logoutParameter) { // 1、消息推送:单点注销 - pushToAllClientByLogoutCall(loginId); + // TODO 需要把对应的 SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_ 记录也删掉 + pushToAllClientByLogoutCall(loginId, logoutParameter); // 2、SaSession 挂载的 Client 端注销会话 SaSession session = getStpLogic().getSessionByLoginId(loginId, false); @@ -571,20 +595,21 @@ public class SaSsoServerTemplate extends SaSsoTemplate { } List scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new); scmList.forEach(scm -> { - notifyClientLogout(loginId, scm, false); + notifyClientLogout(loginId, logoutParameter.getDeviceId(), scm, false); }); // 3、Server 端本身注销 - getStpLogic().logout(loginId); + getStpLogic().logout(loginId, logoutParameter); } /** * 通知指定账号的指定客户端注销 * @param loginId 指定账号 + * @param deviceId 指定设备 id * @param scm 客户端信息对象 * @param autoLogout 是否为超过 maxRegClient 的自动注销 */ - public void notifyClientLogout(Object loginId, SaSsoClientInfo scm, boolean autoLogout) { + public void notifyClientLogout(Object loginId, String deviceId, SaSsoClientInfo scm, boolean autoLogout) { // 如果给个null值,不进行任何操作 if(scm == null || scm.mode != SaSsoConsts.SSO_MODE_3) { @@ -601,6 +626,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate { Map paramsMap = new TreeMap<>(); paramsMap.put(paramName.client, scm.getClient()); paramsMap.put(paramName.loginId, loginId); + paramsMap.put(paramName.deviceId, deviceId); paramsMap.put(paramName.autoLogout, autoLogout); String signParamsStr = getSignTemplate(scm.getClient()).addSignParamsAndJoin(paramsMap); @@ -680,14 +706,16 @@ public class SaSsoServerTemplate extends SaSsoTemplate { * 向所有 Client 推送消息:单点注销回调 * * @param loginId / + * @param logoutParameter 注销参数 */ - public void pushToAllClientByLogoutCall(Object loginId) { + public void pushToAllClientByLogoutCall(Object loginId, SaLogoutParameter logoutParameter) { List npClients = getNeedPushClients(); for (SaSsoClientModel client : npClients) { if(client.getIsSlo()) { SaSsoMessage message = new SaSsoMessage(); message.setType(SaSsoConsts.MESSAGE_LOGOUT_CALL); message.set(paramName.loginId, loginId); + message.set(paramName.deviceId, logoutParameter.getDeviceId()); pushMessage(client, message); } } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoUtil.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoUtil.java index e98c59ed..f7b23bdd 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoUtil.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoUtil.java @@ -15,6 +15,7 @@ */ package cn.dev33.satoken.sso.template; +import cn.dev33.satoken.sso.model.TicketModel; import cn.dev33.satoken.sso.processor.SaSsoClientProcessor; import cn.dev33.satoken.sso.processor.SaSsoServerProcessor; @@ -29,15 +30,17 @@ import java.util.Map; public class SaSsoUtil { // ---------------------- Ticket 操作 ---------------------- - + /** - * 根据 账号id 创建一个 Ticket码 - * @param loginId 账号id - * @param client 客户端标识 - * @return Ticket码 + * 根据参数创建一个 ticket 码 + * + * @param client 客户端标识 + * @param loginId 账号 id + * @param deviceId 设备 id + * @return Ticket码 */ - public static String createTicket(Object loginId, String client) { - return SaSsoServerProcessor.instance.ssoServerTemplate.createTicket(loginId, client); + public static String createTicket(String client, Object loginId, String deviceId) { + return SaSsoServerProcessor.instance.ssoServerTemplate.createTicket(client, loginId, deviceId); } /** @@ -78,22 +81,22 @@ public class SaSsoUtil { } /** - * 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除 + * 校验 Ticket,无效 ticket 会抛出异常,如果此ticket是有效的,则立即删除 * @param ticket Ticket码 * @return 账号id */ - public static Object checkTicket(String ticket) { - return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket); + public static TicketModel checkTicket(String ticket) { + return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicketParamAndDelete(ticket); } /** - * 校验ticket码,获取账号id,如果此ticket是有效的,则立即删除 + * 校验ticket码,无效 ticket 会抛出异常,如果此ticket是有效的,则立即删除 * @param ticket Ticket码 * @param client client 标识 * @return 账号id */ - public static Object checkTicket(String ticket, String client) { - return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, client); + public static TicketModel checkTicket(String ticket, String client) { + return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicketParamAndDelete(ticket, client); } /** @@ -160,14 +163,16 @@ public class SaSsoUtil { } /** - * 构建URL:Server端向Client下放ticket的地址 - * @param loginId 账号id - * @param client 客户端标识 - * @param redirect Client端提供的重定向地址 - * @return see note + * 构建 URL:sso-server 端向 sso-client 下放 ticket 的地址 + * + * @param client 客户端标识 + * @param redirect sso-client 端的重定向地址 + * @param loginId 账号 id + * @param tokenValue 会话 token + * @return / */ - public static String buildRedirectUrl(Object loginId, String client, String redirect) { - return SaSsoServerProcessor.instance.ssoServerTemplate.buildRedirectUrl(loginId, client, redirect); + public static String buildRedirectUrl(String client, String redirect, Object loginId, String tokenValue) { + return SaSsoServerProcessor.instance.ssoServerTemplate.buildRedirectUrl(client, redirect, loginId, tokenValue); } /**