diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml index 4e2f6b70..fbfa451c 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/pom.xml @@ -57,12 +57,12 @@ org.springframework.boot spring-boot-starter-thymeleaf - - + + - com.dtflys.forest - forest-spring-boot-starter - 1.5.26 + cn.dev33 + sa-token-forest + ${sa-token.version} 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 6e2e64ee..5986a9a2 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 @@ -6,7 +6,6 @@ import cn.dev33.satoken.sso.config.SaSsoServerConfig; import cn.dev33.satoken.sso.processor.SaSsoServerProcessor; 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; @@ -50,19 +49,7 @@ public class SsoServerController { } return SaResult.error("登录失败!"); }; - - // 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉) - ssoServer.sendHttp = url -> { - try { - System.out.println("------ 发起请求:" + url); - String resStr = Forest.get(url).executeAsString(); - System.out.println("------ 请求结果:" + resStr); - return resStr; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - }; + } // 示例:获取数据接口(用于在模式三下,为 client 端开放拉取数据的接口) 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 dcea68c8..b8be854d 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 @@ -35,8 +35,9 @@ sa-token: # 应用 sso-client3,采用模式三对接 (跨域、跨Redis) sso-client3: client: sso-client3 - secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor allow-url: "*" + secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + push-url: http://sa-sso-client1.com:9003/sso/pushC # ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明) diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java index 361d1cfb..56eda97d 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java @@ -5,7 +5,6 @@ import cn.dev33.satoken.sso.config.SaSsoClientConfig; import cn.dev33.satoken.sso.processor.SaSsoClientProcessor; 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; @@ -43,13 +42,7 @@ public class SsoClientController { // 配置SSO相关参数 @Autowired private void configSso(SaSsoClientConfig ssoClient) { - // 配置Http请求处理器 - ssoClient.sendHttp = url -> { - System.out.println("------ 发起请求:" + url); - String resStr = Forest.get(url).executeAsString(); - System.out.println("------ 请求结果:" + resStr); - return resStr; - }; + } // 全局异常拦截 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml index 13a2f663..7d328d7f 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client/pom.xml @@ -53,15 +53,16 @@ org.apache.commons commons-pool2 - - + + - com.dtflys.forest - forest-spring-boot-starter - 1.5.26 + cn.dev33 + sa-token-forest + ${sa-token.version} - - + + + 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 2492688a..37578649 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 @@ -5,7 +5,6 @@ import cn.dev33.satoken.sso.processor.SaSsoClientProcessor; import cn.dev33.satoken.sso.template.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; @@ -45,13 +44,7 @@ public class SsoClientController { // 配置SSO相关参数 @Autowired private void configSso(SaSsoClientConfig ssoClient) { - // 配置Http请求处理器 - ssoClient.sendHttp = url -> { - System.out.println("------ 发起请求:" + url); - String resStr = Forest.get(url).executeAsString(); - System.out.println("------ 请求结果:" + resStr); - return resStr; - }; + } // 查询我的账号信息 diff --git a/sa-token-plugin/sa-token-forest/src/main/java/cn/dev33/satoken/http/SaHttpTemplateForForest.java b/sa-token-plugin/sa-token-forest/src/main/java/cn/dev33/satoken/http/SaHttpTemplateForForest.java index 9e24b800..e1e1d998 100644 --- a/sa-token-plugin/sa-token-forest/src/main/java/cn/dev33/satoken/http/SaHttpTemplateForForest.java +++ b/sa-token-plugin/sa-token-forest/src/main/java/cn/dev33/satoken/http/SaHttpTemplateForForest.java @@ -21,7 +21,7 @@ import com.dtflys.forest.Forest; import java.util.Map; /** - * Http 转换器, Forest 版实现 + * Http 请求处理器, Forest 版实现 * * @author click33 * @since 1.43.0 diff --git a/sa-token-plugin/sa-token-forest/src/main/java/cn/dev33/satoken/plugin/SaTokenPluginForForest.java b/sa-token-plugin/sa-token-forest/src/main/java/cn/dev33/satoken/plugin/SaTokenPluginForForest.java index dad3712a..ff1ffc64 100644 --- a/sa-token-plugin/sa-token-forest/src/main/java/cn/dev33/satoken/plugin/SaTokenPluginForForest.java +++ b/sa-token-plugin/sa-token-forest/src/main/java/cn/dev33/satoken/plugin/SaTokenPluginForForest.java @@ -17,6 +17,7 @@ package cn.dev33.satoken.plugin; import cn.dev33.satoken.SaManager; import cn.dev33.satoken.http.SaHttpTemplateForForest; +import com.dtflys.forest.config.ForestConfiguration; /** * SaToken 插件安装:Http 请求处理器 - Forest 版 @@ -28,6 +29,10 @@ public class SaTokenPluginForForest implements SaTokenPlugin { @Override public void install() { + // 关闭 Forest 默认日志打印 + ForestConfiguration.getDefaultConfiguration().setLogEnabled(false); + + // 设置 Forest 作为 Http 请求处理器 SaManager.setSaHttpTemplate(new SaHttpTemplateForForest()); } 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 1e6abd9f..3d4537c7 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 @@ -16,9 +16,6 @@ package cn.dev33.satoken.sso.config; -import cn.dev33.satoken.sso.error.SaSsoErrorCode; -import cn.dev33.satoken.sso.exception.SaSsoException; -import cn.dev33.satoken.sso.function.SendHttpFunction; import cn.dev33.satoken.sso.function.TicketResultHandleFunction; import cn.dev33.satoken.util.SaFoxUtil; @@ -40,12 +37,12 @@ public class SaSsoClientConfig implements Serializable { public String mode = ""; /** - * 当前 Client 名称标识,用于和 ticket 码的互相锁定 + * 当前 Client 标识 */ public String client; /** - * 配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置 + * 配置 Server 端主机总地址 */ public String serverUrl; @@ -54,11 +51,6 @@ public class SaSsoClientConfig implements Serializable { */ public String authUrl = "/sso/auth"; - /** - * 单独配置 Server 端的 ticket 校验地址 - */ - public String checkTicketUrl = "/sso/checkTicket"; - /** * 单独配置 Server 端查询数据 getData 地址 */ @@ -69,6 +61,11 @@ public class SaSsoClientConfig implements Serializable { */ public String sloUrl = "/sso/signout"; + /** + * 单独配置 Server 端推送消息地址 + */ + public String pushUrl = "/sso/pushS"; + /** * 配置当前 Client 端的登录地址(为空时自动获取) */ @@ -80,12 +77,17 @@ public class SaSsoClientConfig implements Serializable { public String currSsoLogoutCall; /** - * 是否打开单点注销功能 + * 是否打开单点注销功能 (为 true 时,接收单点注销回调消息推送) */ public Boolean isSlo = true; /** - * 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、拉取数据getData) + * 是否注册单点登录注销回调 (为 true 时,登录时附带单点登录回调地址,并且开放 /sso/logoutCall 地址) + */ + public Boolean regLogoutCall = false; + + /** + * 是否打开模式三(此值为 true 时将使用 http 请求校验 ticket 值) */ public Boolean isHttp = false; @@ -100,6 +102,47 @@ public class SaSsoClientConfig implements Serializable { public Boolean isCheckSign = true; + // 额外添加的一些函数 + + /** + * @return 获取拼接url:Server 端单点登录授权地址 + */ + public String splicingAuthUrl() { + return SaFoxUtil.spliceTwoUrl(getServerUrl(), getAuthUrl()); + } + + /** + * @return 获取拼接url:Server 端查询数据 getData 地址 + */ + public String splicingGetDataUrl() { + return SaFoxUtil.spliceTwoUrl(getServerUrl(), getGetDataUrl()); + } + + /** + * @return 获取拼接url:Server 端单点注销地址 + */ + public String splicingSloUrl() { + return SaFoxUtil.spliceTwoUrl(getServerUrl(), getSloUrl()); + } + + /** + * @return 获取拼接url:单独配置 Server 端推送消息地址 + */ + public String splicingPushUrl() { + return SaFoxUtil.spliceTwoUrl(getServerUrl(), getPushUrl()); + } + + + // -------------------- 所有回调函数 -------------------- + + /** + * SSO-Client端:自定义校验 ticket 返回值的处理逻辑 (每次从认证中心获取校验 ticket 的结果后调用) + *

参数:loginId, back + *

返回值:返回给前端的值 + */ + public TicketResultHandleFunction ticketResultHandle = null; + + // get set /** @@ -183,22 +226,6 @@ public class SaSsoClientConfig implements Serializable { return this; } - /** - * @return 配置的 Server 端的 ticket 校验地址 - */ - public String getCheckTicketUrl() { - return checkTicketUrl; - } - - /** - * @param checkTicketUrl 配置 Server 端的 ticket 校验地址 - * @return 对象自身 - */ - public SaSsoClientConfig setCheckTicketUrl(String checkTicketUrl) { - this.checkTicketUrl = checkTicketUrl; - return this; - } - /** * @return Server 端查询数据 getData 地址 */ @@ -231,6 +258,26 @@ public class SaSsoClientConfig implements Serializable { return this; } + /** + * 获取 单独配置 Server 端推送消息地址 + * + * @return / + */ + public String getPushUrl() { + return this.pushUrl; + } + + /** + * 设置 单独配置 Server 端推送消息地址 + * + * @param pushUrl / + * @return 对象自身 + */ + public SaSsoClientConfig setPushUrl(String pushUrl) { + this.pushUrl = pushUrl; + return this; + } + /** * @return 配置当前 Client 端的登录地址(为空时自动获取) */ @@ -318,6 +365,26 @@ public class SaSsoClientConfig implements Serializable { return this; } + /** + * 获取 是否注册单点登录注销回调 (为 true 时,登录时附带单点登录回调地址,并且开放 /sso/logoutCall 地址) + * + * @return / + */ + public Boolean getRegLogoutCall() { + return this.regLogoutCall; + } + + /** + * 设置 是否注册单点登录注销回调 (为 true 时,登录时附带单点登录回调地址,并且开放 /sso/logoutCall 地址) + * + * @param regLogoutCall / + * @return / + */ + public SaSsoClientConfig setRegLogoutCall(Boolean regLogoutCall) { + this.regLogoutCall = regLogoutCall; + return this; + } + @Override public String toString() { return "SaSsoClientConfig [" @@ -325,63 +392,16 @@ public class SaSsoClientConfig implements Serializable { + ", client=" + client + ", serverUrl=" + serverUrl + ", authUrl=" + authUrl - + ", checkTicketUrl=" + checkTicketUrl + ", getDataUrl=" + getDataUrl + ", sloUrl=" + sloUrl + ", currSsoLogin=" + currSsoLogin + ", currSsoLogoutCall=" + currSsoLogoutCall - + ", isSlo=" + isSlo + ", isHttp=" + isHttp + + ", isSlo=" + isSlo + + ", regLogoutCall=" + regLogoutCall + ", secretKey=" + secretKey + ", isCheckSign=" + isCheckSign + "]"; } - // 额外添加的一些函数 - - /** - * @return 获取拼接url:Server 端单点登录授权地址 - */ - public String splicingAuthUrl() { - return SaFoxUtil.spliceTwoUrl(getServerUrl(), getAuthUrl()); - } - - /** - * @return 获取拼接url:Server 端的 ticket 校验地址 - */ - public String splicingCheckTicketUrl() { - return SaFoxUtil.spliceTwoUrl(getServerUrl(), getCheckTicketUrl()); - } - - /** - * @return 获取拼接url:Server 端查询数据 getData 地址 - */ - public String splicingGetDataUrl() { - return SaFoxUtil.spliceTwoUrl(getServerUrl(), getGetDataUrl()); - } - - /** - * @return 获取拼接url:Server 端单点注销地址 - */ - public String splicingSloUrl() { - return SaFoxUtil.spliceTwoUrl(getServerUrl(), getSloUrl()); - } - - - // -------------------- 所有回调函数 -------------------- - - /** - * SSO-Client端:自定义校验 ticket 返回值的处理逻辑 (每次从认证中心获取校验 ticket 的结果后调用) - *

参数:loginId, back - *

返回值:返回给前端的值 - */ - public TicketResultHandleFunction ticketResultHandle = null; - - /** - * SSO-Client端:发送Http请求的处理函数 - */ - public SendHttpFunction sendHttp = url -> { - throw new SaSsoException("请配置 Http 请求处理器").setCode(SaSsoErrorCode.CODE_30010); - }; - } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientModel.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientModel.java index d907f23d..b1e6a909 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientModel.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/config/SaSsoClientModel.java @@ -42,6 +42,11 @@ public class SaSsoClientModel implements Serializable { */ public String allowUrl = "*"; + /** + * 是否打开模式三(此值为 true 时使用 http 调用方式进行消息通知) + */ + public Boolean isHttp = false; + /** * 是否打开单点注销功能 */ @@ -52,7 +57,18 @@ public class SaSsoClientModel implements Serializable { */ public String secretKey; - // 额外方法 + /** + * 此 Client 端主机总地址 + */ + public String serverUrl; + + /** + * 此 Client 端推送消息的地址 + */ + public String pushUrl = "/sso/pushC"; + + + // 额外添加的一些函数 /** * 以数组形式写入允许的授权回调地址 @@ -64,6 +80,25 @@ public class SaSsoClientModel implements Serializable { return this; } + /** + * 获取拼接 url:此 Client 端推送消息的地址 + * + * @return / + */ + public String splicingNoticeUrl() { + return SaFoxUtil.spliceTwoUrl(getServerUrl(), getPushUrl()); + } + + /** + * 判断是否配置了有效地推送地址 + * + * @return / + */ + public boolean isValidNoticeUrl() { + return SaFoxUtil.isUrl(splicingNoticeUrl()); + } + + // get set /** @@ -102,6 +137,22 @@ public class SaSsoClientModel implements Serializable { return this; } + /** + * @return isHttp 是否打开模式三 + */ + public Boolean getIsHttp() { + return isHttp; + } + + /** + * @param isHttp 是否打开模式三 + * @return 对象自身 + */ + public SaSsoClientModel setIsHttp(Boolean isHttp) { + this.isHttp = isHttp; + return this; + } + /** * @return 是否打开单点注销功能 */ @@ -138,13 +189,56 @@ public class SaSsoClientModel implements Serializable { return this; } + /** + * 获取 此 Client 端主机总地址 + * + * @return serverUrl 此 Client 端主机总地址 + */ + public String getServerUrl() { + return this.serverUrl; + } + + /** + * 设置 此 Client 端主机总地址 + * + * @param serverUrl 此 Client 端主机总地址 + * @return 对象自身 + */ + public SaSsoClientModel setServerUrl(String serverUrl) { + this.serverUrl = serverUrl; + return this; + } + + /** + * 获取 此 Client 端推送消息的地址 + * + * @return noticeUrl 此 Client 端推送消息的地址 + */ + public String getPushUrl() { + return this.pushUrl; + } + + /** + * 设置 此 Client 端推送消息的地址 + * + * @param pushUrl 此 Client 端推送消息的地址 + * @return 对象自身 + */ + public SaSsoClientModel setPushUrl(String pushUrl) { + this.pushUrl = pushUrl; + return this; + } + @Override public String toString() { return "SaSsoClientModel [" + "client=" + client + ", allowUrl=" + allowUrl + ", isSlo=" + isSlo + + ", isHttp=" + isHttp + ", secretKey=" + secretKey + + ", serverUrl=" + serverUrl + + ", pushUrl=" + pushUrl + "]"; } 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 cedde8fb..df59d9ce 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 @@ -16,12 +16,9 @@ package cn.dev33.satoken.sso.config; -import cn.dev33.satoken.sso.error.SaSsoErrorCode; -import cn.dev33.satoken.sso.exception.SaSsoException; import cn.dev33.satoken.sso.function.CheckTicketAppendDataFunction; import cn.dev33.satoken.sso.function.DoLoginHandleFunction; import cn.dev33.satoken.sso.function.NotLoginViewFunction; -import cn.dev33.satoken.sso.function.SendHttpFunction; import cn.dev33.satoken.sso.template.SaSsoServerTemplate; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; @@ -54,11 +51,6 @@ public class SaSsoServerConfig implements Serializable { */ public long ticketTimeout = 60 * 5; - /** - * 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) - */ - public String allowUrl = "*"; - /** * 主页路由:在 /sso/auth 登录后不指定 redirect 参数的情况下默认跳转的路由 */ @@ -69,41 +61,43 @@ public class SaSsoServerConfig implements Serializable { */ public Boolean isSlo = true; - /** - * 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo) - */ - public Boolean isHttp = false; - /** * 是否在每次下发 ticket 时,自动续期 token 的有效期(根据全局 timeout 值) */ public Boolean autoRenewTimeout = false; /** - * 在 Access-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 + * 在 Account-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 */ public int maxRegClient = 32; - /** - * 是否允许匿名 Client 接入 - */ - public Boolean allowAnonClient = true; - /** * 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true) */ public Boolean isCheckSign = true; - /** - * API 调用签名秘钥 - */ - public String secretKey; - /** * Client 信息配置列表 */ public Map clients = new LinkedHashMap<>(); + // 匿名 Client 相关配置 + + /** + * 是否允许匿名 Client 接入 + */ + public Boolean allowAnonClient = true; + + /** + * 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) (匿名 client 使用) + */ + public String allowUrl = "*"; + + /** + * API 调用签名秘钥 (全局默认 + 匿名 client 使用) + */ + public String secretKey; + // 额外方法 @@ -128,6 +122,31 @@ public class SaSsoServerConfig implements Serializable { } + // -------------------- 所有回调函数 -------------------- + + + /** + * SSO-Server端:未登录时返回的View + */ + public NotLoginViewFunction notLoginView = () -> { + return "当前会话在SSO-Server认证中心尚未登录(当前未配置登录视图)"; + }; + + /** + * SSO-Server端:登录函数 + */ + public DoLoginHandleFunction doLoginHandle = (name, pwd) -> { + return SaResult.error(); + }; + + /** + * SSO-Server端:在校验 ticket 后,给 sso-client 端追加返回信息的函数 + */ + public CheckTicketAppendDataFunction checkTicketAppendData = (loginId, result) -> { + return result; + }; + + // get set /** @@ -165,14 +184,14 @@ public class SaSsoServerConfig implements Serializable { } /** - * @return 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) + * @return 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) (匿名 client 使用) */ public String getAllowUrl() { return allowUrl; } /** - * @param allowUrl 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) + * @param allowUrl 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) (匿名 client 使用) * @return 对象自身 */ public SaSsoServerConfig setAllowUrl(String allowUrl) { @@ -217,22 +236,6 @@ public class SaSsoServerConfig implements Serializable { return this; } - /** - * @return isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo) - */ - public Boolean getIsHttp() { - return isHttp; - } - - /** - * @param isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo) - * @return 对象自身 - */ - public SaSsoServerConfig setIsHttp(Boolean isHttp) { - this.isHttp = isHttp; - return this; - } - /** * @return 是否在每次下发 ticket 时,自动续期 token 的有效期(根据全局 timeout 值) */ @@ -250,14 +253,14 @@ public class SaSsoServerConfig implements Serializable { } /** - * @return maxLoginClient 在 Access-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 + * @return maxLoginClient 在 Account-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 */ public int getMaxRegClient() { return maxRegClient; } /** - * @param maxRegClient 在 Access-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 + * @param maxRegClient 在 Account-Session 上记录 Client 信息的最高数量(-1=无限),超过此值将进行自动清退处理,先进先出 * @return 对象自身 */ public SaSsoServerConfig setMaxRegClient(int maxRegClient) { @@ -304,7 +307,7 @@ public class SaSsoServerConfig implements Serializable { } /** - * 获取 API 调用签名秘钥 + * 获取 API 调用签名秘钥 (全局默认 + 匿名 client 使用) * * @return / */ @@ -313,7 +316,7 @@ public class SaSsoServerConfig implements Serializable { } /** - * 设置 API 调用签名秘钥 + * 设置 API 调用签名秘钥 (全局默认 + 匿名 client 使用) * * @param secretKey / * @return 对象自身 @@ -351,7 +354,6 @@ public class SaSsoServerConfig implements Serializable { + ", allowUrl=" + allowUrl + ", homeRoute=" + homeRoute + ", isSlo=" + isSlo - + ", isHttp=" + isHttp + ", autoRenewTimeout=" + autoRenewTimeout + ", maxRegClient=" + maxRegClient + ", isCheckSign=" + isCheckSign @@ -362,107 +364,4 @@ public class SaSsoServerConfig implements Serializable { } - // -------------------- 所有回调函数 -------------------- - - - /** - * SSO-Server端:未登录时返回的View - */ - public NotLoginViewFunction notLoginView = () -> { - return "当前会话在SSO-Server认证中心尚未登录(当前未配置登录视图)"; - }; - - /** - * SSO-Server端:登录函数 - */ - public DoLoginHandleFunction doLoginHandle = (name, pwd) -> { - return SaResult.error(); - }; - - /** - * SSO-Server端:在校验 ticket 后,给 sso-client 端追加返回信息的函数 - */ - public CheckTicketAppendDataFunction checkTicketAppendData = (loginId, result) -> { - return result; - }; - - /** - * SSO-Server端:发送Http请求的处理函数 - */ - public SendHttpFunction sendHttp = url -> { - throw new SaSsoException("请配置 Http 请求处理器").setCode(SaSsoErrorCode.CODE_30010); - }; - - /** - * 获取 SSO-Server端:未登录时返回的View - * - * @return notLoginView SSO-Server端:未登录时返回的View - */ - public NotLoginViewFunction getNotLoginView() { - return this.notLoginView; - } - - /** - * 设置 SSO-Server端:未登录时返回的View - * - * @param notLoginView SSO-Server端:未登录时返回的View - */ - public void setNotLoginView(NotLoginViewFunction notLoginView) { - this.notLoginView = notLoginView; - } - - /** - * 获取 SSO-Server端:登录函数 - * - * @return doLoginHandle SSO-Server端:登录函数 - */ - public DoLoginHandleFunction getDoLoginHandle() { - return this.doLoginHandle; - } - - /** - * 设置 SSO-Server端:登录函数 - * - * @param doLoginHandle SSO-Server端:登录函数 - */ - public void setDoLoginHandle(DoLoginHandleFunction doLoginHandle) { - this.doLoginHandle = doLoginHandle; - } - - /** - * 获取 SSO-Server端:在校验 ticket 后,给 sso-client 端追加返回信息的函数 - * - * @return checkTicketAppendData SSO-Server端:在校验 ticket 后,给 sso-client 端追加返回信息的函数 - */ - public CheckTicketAppendDataFunction getCheckTicketAppendData() { - return this.checkTicketAppendData; - } - - /** - * 设置 SSO-Server端:在校验 ticket 后,给 sso-client 端追加返回信息的函数 - * - * @param checkTicketAppendData SSO-Server端:在校验 ticket 后,给 sso-client 端追加返回信息的函数 - */ - public void setCheckTicketAppendData(CheckTicketAppendDataFunction checkTicketAppendData) { - this.checkTicketAppendData = checkTicketAppendData; - } - - /** - * 获取 SSO-Server端:发送Http请求的处理函数 - * - * @return sendHttp SSO-Server端:发送Http请求的处理函数 - */ - public SendHttpFunction getSendHttp() { - return this.sendHttp; - } - - /** - * 设置 SSO-Server端:发送Http请求的处理函数 - * - * @param sendHttp SSO-Server端:发送Http请求的处理函数 - */ - public void setSendHttp(SendHttpFunction sendHttp) { - this.sendHttp = sendHttp; - } - } 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 49b67781..e1bb6fe5 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 @@ -68,4 +68,13 @@ public interface SaSsoErrorCode { /** 无效的 allow-url 配置 */ int CODE_30015 = 30015; + /** 未能找到指定类型的消息处理器 */ + int CODE_30021 = 30021; + + /** 消息类型不能为空 */ + int CODE_30022 = 30022; + + /** 无效的消息推送地址 */ + int CODE_30023 = 30023; + } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/SaSsoMessage.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/SaSsoMessage.java new file mode 100644 index 00000000..41098682 --- /dev/null +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/SaSsoMessage.java @@ -0,0 +1,111 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.sso.message; + + +import cn.dev33.satoken.application.SaSetValueInterface; +import cn.dev33.satoken.sso.error.SaSsoErrorCode; +import cn.dev33.satoken.sso.exception.SaSsoException; +import cn.dev33.satoken.util.SaFoxUtil; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Sa-Token SSO 消息 Model + * + * @author click33 + * @since 1.43.0 + */ +public class SaSsoMessage extends LinkedHashMap implements SaSetValueInterface, Serializable { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * KEY:TYPE + */ + public static final String MSG_TYPE = "msg_type"; + + public SaSsoMessage() { + + } + + /** + * 构造函数 + * @param type 消息类型 + */ + public SaSsoMessage(String type) { + setType(type); + } + + /** + * 构造函数 + * @param map 消息参数 + */ + public SaSsoMessage(Map map) { + this.putAll(map); + } + + /** + * 获取消息类型 + * @return / + */ + public String getType() { + return getString(MSG_TYPE); + } + + /** + * 设置消息类型 + * @param type / + * @return / + */ + public SaSsoMessage setType(String type) { + return set(MSG_TYPE, type); + } + + /** + * 校验消息类型 + */ + public void checkType() { + if(SaFoxUtil.isEmpty(getString(MSG_TYPE))) { + throw new SaSsoException("消息类型不可为空").setCode(SaSsoErrorCode.CODE_30022); + } + } + + // ----------- + + @Override + public Object get(String key) { + return super.get(key); + } + + @Override + public SaSsoMessage set(String key, Object value) { + super.put(key, value); + return this; + } + + @Override + public SaSsoMessage delete(String key) { + super.remove(key); + return this; + } + +} diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/SaSsoMessageHolder.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/SaSsoMessageHolder.java new file mode 100644 index 00000000..0b68cde7 --- /dev/null +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/SaSsoMessageHolder.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.sso.message; + + +import cn.dev33.satoken.sso.error.SaSsoErrorCode; +import cn.dev33.satoken.sso.exception.SaSsoException; +import cn.dev33.satoken.sso.message.handle.SaSsoMessageHandle; +import cn.dev33.satoken.sso.template.SaSsoTemplate; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Sa-Token SSO 消息处理器持有者 + * + * @author click33 + * @since 1.43.0 + */ +public class SaSsoMessageHolder { + + /** + * 所有消息处理器的集合 + */ + public final Map messageHandleMap = new LinkedHashMap<>(); + + /** + * 判断是否具有指定类型的消息处理器 + * + * @param type 消息类型 + * @return / + */ + public boolean hasHandle(String type) { + return messageHandleMap.containsKey(type); + } + + /** + * 删除指定类型的消息处理器 + * + * @param type 消息类型 + */ + public SaSsoMessageHolder removeHandle(String type) { + messageHandleMap.remove(type); + return this; + } + + /** + * 添加指定类型的消息处理器 + * + * @param handle / + */ + public SaSsoMessageHolder addHandle(SaSsoMessageHandle handle) { + messageHandleMap.put(handle.getHandlerType(), handle); + return this; + } + + /** + * 获取指定类型的消息处理器 + * + * @param type / + */ + public SaSsoMessageHandle getHandle(String type) { + return messageHandleMap.get(type); + } + + /** + * 处理指定消息 + * + * @param ssoTemplate / + * @param message / + * @return / + */ + public Object handleMessage(SaSsoTemplate ssoTemplate, SaSsoMessage message) { + SaSsoMessageHandle handle = messageHandleMap.get(message.getType()); + if(handle == null) { + throw new SaSsoException("未能找到消息处理器: " + message.getType()).setCode(SaSsoErrorCode.CODE_30021); + } + return handle.handle(ssoTemplate, message); + } + +} diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/SaSsoMessageHandle.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/SaSsoMessageHandle.java new file mode 100644 index 00000000..32944139 --- /dev/null +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/SaSsoMessageHandle.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.sso.message.handle; + + +import cn.dev33.satoken.sso.message.SaSsoMessage; +import cn.dev33.satoken.sso.template.SaSsoTemplate; + +/** + * Sa-Token SSO 消息 处理器 + * + * @author click33 + * @since 1.43.0 + */ +public interface SaSsoMessageHandle { + + /** + * 获取所要处理的消息类型 + * + * @return / + */ + String getHandlerType(); + + /** + * 执行方法 + * + * @param ssoTemplate / + * @param message / + * @return / + */ + Object handle(SaSsoTemplate ssoTemplate, SaSsoMessage message); + +} 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 new file mode 100644 index 00000000..f728f4af --- /dev/null +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/client/SaSsoMessageLogoutCallHandle.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.dev33.satoken.sso.message.handle.client; + + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.sso.message.SaSsoMessage; +import cn.dev33.satoken.sso.message.handle.SaSsoMessageHandle; +import cn.dev33.satoken.sso.name.ParamName; +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.util.SaResult; + +/** + * Sa-Token SSO 消息 处理器 - sso-client 端:处理 单点注销回调 的请求 + * + * @author click33 + * @since 1.43.0 + */ +public class SaSsoMessageLogoutCallHandle implements SaSsoMessageHandle { + + /** + * 获取所要处理的消息类型 + * + * @return / + */ + public String getHandlerType() { + return SaSsoConsts.MESSAGE_LOGOUT_CALL; + } + + /** + * 执行方法 + * + * @param ssoTemplate / + * @param message / + * @return / + */ + public Object handle(SaSsoTemplate ssoTemplate, SaSsoMessage message) { + SaSsoClientTemplate ssoClientTemplate = (SaSsoClientTemplate) ssoTemplate; + if( ! ssoClientTemplate.getClientConfig().getIsSlo()) { + return SaResult.error("当前 sso-client 端未开启单点注销功能"); + } + + // 获取对象 + SaRequest req = SaHolder.getRequest(); + StpLogic stpLogic = ssoClientTemplate.getStpLogic(); + ParamName paramName = ssoClientTemplate.paramName; + + // 获取参数 + String loginId = req.getParamNotNull(paramName.loginId); + + // 注销当前应用端会话 + stpLogic.logout(loginId); + + // 响应 + 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 new file mode 100644 index 00000000..22ae41c0 --- /dev/null +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/server/SaSsoMessageCheckTicketHandle.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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.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.util.SaResult; + +/** + * Sa-Token SSO 消息 处理器 - sso-server 端:处理校验 ticket 的请求 + * + * @author click33 + * @since 1.43.0 + */ +public class SaSsoMessageCheckTicketHandle implements SaSsoMessageHandle { + + /** + * 获取所要处理的消息类型 + * + * @return / + */ + public String getHandlerType() { + return SaSsoConsts.MESSAGE_CHECK_TICKET; + } + + /** + * 执行方法 + * + * @param ssoTemplate / + * @param message / + * @return / + */ + public Object handle(SaSsoTemplate ssoTemplate, SaSsoMessage message) { + SaSsoServerTemplate ssoServerTemplate = (SaSsoServerTemplate) ssoTemplate; + ParamName paramName = ssoServerTemplate.paramName; + + // 1、获取参数 + SaRequest req = SaHolder.getRequest(); + SaSsoServerConfig ssoServerConfig = ssoServerTemplate.getServerConfig(); + 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); + } + + // 3、校验签名 +// if(ssoServerConfig.getIsCheckSign()) { +// ssoServerTemplate.getSignTemplate(client).checkRequest(req, paramName.client, paramName.ticket, paramName.ssoLogoutCall); +// } else { +// SaSsoManager.printNoCheckSignWarningByRuntime(); +// } + + // 4、校验ticket,获取 loginId + Object loginId = ssoServerTemplate.checkTicket(ticket, client); + if(SaFoxUtil.isEmpty(loginId)) { + return SaResult.error("无效ticket:" + ticket); + } + + // 5、注册此客户端的单点注销回调URL + ssoServerTemplate.registerSloCallbackUrl(loginId, client, sloCallback); + + // 6、给 client 端响应结果 + long remainSessionTimeout = ssoServerTemplate.getStpLogic().getSessionTimeoutByLoginId(loginId); + SaResult result = SaResult.data(loginId).set(paramName.remainSessionTimeout, remainSessionTimeout); + 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 new file mode 100644 index 00000000..43b83d57 --- /dev/null +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/message/handle/server/SaSsoMessageSignoutHandle.java @@ -0,0 +1,72 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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.message.SaSsoMessage; +import cn.dev33.satoken.sso.message.handle.SaSsoMessageHandle; +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.SaResult; + +/** + * Sa-Token SSO 消息 处理器 - sso-server 端:处理 单点注销 的请求 + * + * @author click33 + * @since 1.43.0 + */ +public class SaSsoMessageSignoutHandle implements SaSsoMessageHandle { + + /** + * 获取所要处理的消息类型 + * + * @return / + */ + public String getHandlerType() { + return SaSsoConsts.MESSAGE_SIGNOUT; + } + + /** + * 执行方法 + * + * @param ssoTemplate / + * @param message / + * @return / + */ + public Object handle(SaSsoTemplate ssoTemplate, SaSsoMessage message) { + SaSsoServerTemplate ssoServerTemplate = (SaSsoServerTemplate) ssoTemplate; + ParamName paramName = ssoServerTemplate.paramName; + + if( ! ssoServerTemplate.getServerConfig().getIsSlo()) { + return SaResult.error("当前 sso-server 端未开启单点注销功能"); + } + + // 获取参数 + SaRequest req = SaHolder.getRequest(); + String loginId = req.getParam(paramName.loginId); + + // step.2 单点注销 + ssoServerTemplate.ssoLogout(loginId); + + // 响应 + return SaResult.ok(); + } + +} diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ApiName.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ApiName.java index 8d2545fb..7d877d86 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ApiName.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/name/ApiName.java @@ -32,6 +32,9 @@ public class ApiName { /** SSO-Server端:校验ticket 获取账号id */ public String ssoCheckTicket = "/sso/checkTicket"; + /** SSO-Server端:接受推送消息 */ + public String ssoPushS = "/sso/pushS"; + /** SSO-Server端:获取userinfo */ public String ssoUserinfo = "/sso/userinfo"; @@ -46,7 +49,10 @@ public class ApiName { /** SSO-Client端:单点注销的回调 */ public String ssoLogoutCall = "/sso/logoutCall"; - + + /** SSO-Client端:接受推送消息 */ + public String ssoPushC = "/sso/pushC"; + /** * 批量修改path,新增固定前缀 * @param prefix 示例值:/sso-user、/sso-admin @@ -56,10 +62,12 @@ public class ApiName { this.ssoAuth = prefix + this.ssoAuth; this.ssoDoLogin = prefix + this.ssoDoLogin; this.ssoCheckTicket = prefix + this.ssoCheckTicket; + this.ssoPushS = prefix + this.ssoPushS; this.ssoUserinfo = prefix + this.ssoUserinfo; this.ssoSignout = prefix + this.ssoSignout; this.ssoLogin = prefix + this.ssoLogin; this.ssoLogout = prefix + this.ssoLogout; + this.ssoPushC = prefix + this.ssoPushC; this.ssoLogoutCall = prefix + this.ssoLogoutCall; return this; } @@ -74,21 +82,30 @@ public class ApiName { this.ssoAuth = this.ssoAuth.replaceFirst(oldPrefix, prefix); this.ssoDoLogin = this.ssoDoLogin.replaceFirst(oldPrefix, prefix); this.ssoCheckTicket = this.ssoCheckTicket.replaceFirst(oldPrefix, prefix); + this.ssoPushS = this.ssoPushS.replaceFirst(oldPrefix, prefix); this.ssoUserinfo = this.ssoUserinfo.replaceFirst(oldPrefix, prefix); this.ssoSignout = this.ssoSignout.replaceFirst(oldPrefix, prefix); this.ssoLogin = this.ssoLogin.replaceFirst(oldPrefix, prefix); this.ssoLogout = this.ssoLogout.replaceFirst(oldPrefix, prefix); + this.ssoPushC = this.ssoPushC.replaceFirst(oldPrefix, prefix); this.ssoLogoutCall = this.ssoLogoutCall.replaceFirst(oldPrefix, prefix); return this; } - @Override public String toString() { - return "ApiName [ssoAuth=" + ssoAuth + ", ssoDoLogin=" + ssoDoLogin + ", ssoCheckTicket=" + ssoCheckTicket - + ", ssoUserinfo=" + ssoUserinfo + ", ssoSignout=" + ssoSignout + ", ssoLogin=" + ssoLogin - + ", ssoLogout=" + ssoLogout + ", ssoLogoutCall=" + ssoLogoutCall + "]"; + return "ApiName{" + + "ssoAuth='" + ssoAuth + '\'' + + ", ssoDoLogin='" + ssoDoLogin + '\'' + + ", ssoCheckTicket='" + ssoCheckTicket + '\'' + + ", ssoPushS='" + ssoPushS + '\'' + + ", ssoUserinfo='" + ssoUserinfo + '\'' + + ", ssoSignout='" + ssoSignout + '\'' + + ", ssoLogin='" + ssoLogin + '\'' + + ", ssoLogout='" + ssoLogout + '\'' + + ", ssoLogoutCall='" + ssoLogoutCall + '\'' + + ", ssoPushC='" + ssoPushC + '\'' + + '}'; } - } 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 672415c5..aa78a3ab 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 @@ -22,6 +22,7 @@ import cn.dev33.satoken.sso.SaSsoManager; import cn.dev33.satoken.sso.config.SaSsoClientConfig; 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.name.ApiName; import cn.dev33.satoken.sso.name.ParamName; @@ -31,6 +32,8 @@ import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; +import java.util.Map; + /** * SSO 请求处理器 (Client端) * @@ -74,8 +77,13 @@ public class SaSsoClientProcessor { return ssoLogout(); } + // ---------- SSO-Client端:接收消息推送 + if(req.isPath(apiName.ssoPushC)) { + return ssoPushC(); + } + // ---------- SSO-Client端:单点注销的回调 [模式三] - if(req.isPath(apiName.ssoLogoutCall) && cfg.getIsSlo() && cfg.getIsHttp()) { + if(req.isPath(apiName.ssoLogoutCall) && cfg.getRegLogoutCall()) { return ssoLogoutCall(); } @@ -154,6 +162,27 @@ public class SaSsoClientProcessor { return SaSsoConsts.NOT_HANDLE; } + /** + * SSO-Client端:接收推送消息 + * + * @return 处理结果 + */ + public Object ssoPushC() { + SaSsoClientConfig ssoClientConfig = ssoClientTemplate.getClientConfig(); + + // 1、校验签名 + Map paramMap = SaHolder.getRequest().getParamMap(); + if(ssoClientConfig.getIsCheckSign()) { + ssoClientTemplate.getSignTemplate(ssoClientConfig.getClient()).checkParamMap(paramMap); + } else { + SaSsoManager.printNoCheckSignWarningByRuntime(); + } + + // 2、处理消息 + SaSsoMessage message = new SaSsoMessage(paramMap); + return ssoClientTemplate.handleMessage(message); + } + /** * SSO-Client端:单点注销 [模式二] * @return 处理结果 @@ -189,8 +218,8 @@ public class SaSsoClientProcessor { } // 调用 sso-server 认证中心单点注销API - String url = ssoClientTemplate.buildSloUrl(stpLogic.getLoginId()); - SaResult result = ssoClientTemplate.request(url); + SaSsoMessage message = ssoClientTemplate.buildSloMessage(stpLogic.getLoginId()); + SaResult result = ssoClientTemplate.pushMessageAsSaResult(message); // 校验响应状态码 if(result.getCode() != null && SaResult.CODE_SUCCESS == result.getCode()) { @@ -224,8 +253,7 @@ public class SaSsoClientProcessor { // 校验参数签名 if(ssoConfig.getIsCheckSign()) { - ssoClientTemplate.getSignTemplate(ssoConfig.getClient()). - checkRequest(req, paramName.loginId, paramName.client, paramName.autoLogout); + ssoClientTemplate.getSignTemplate(ssoConfig.getClient()).checkRequest(req, paramName.loginId, paramName.client, paramName.autoLogout); } else { SaSsoManager.printNoCheckSignWarningByRuntime(); } @@ -256,7 +284,7 @@ public class SaSsoClientProcessor { // 计算当前 sso-client 的单点注销回调地址 String ssoLogoutCall = null; - if(cfg.getIsSlo()) { + if(cfg.getRegLogoutCall()) { // 如果配置了回调地址,就使用配置的值: if(SaFoxUtil.isNotEmpty(cfg.getCurrSsoLogoutCall())) { ssoLogoutCall = cfg.getCurrSsoLogoutCall(); @@ -270,11 +298,9 @@ public class SaSsoClientProcessor { } } - // 构建请求URL - String checkUrl = ssoClientTemplate.buildCheckTicketUrl(ticket, ssoLogoutCall); - // 发起请求 - SaResult result = ssoClientTemplate.request(checkUrl); + SaSsoMessage message = ssoClientTemplate.buildCheckTicketMessage(ticket, ssoLogoutCall); + SaResult result = ssoClientTemplate.pushMessageAsSaResult(message); // 校验 if(result.getCode() != null && result.getCode() == SaResult.CODE_SUCCESS) { 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 168cecc3..f8e97c22 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 @@ -22,6 +22,7 @@ import cn.dev33.satoken.sso.SaSsoManager; import cn.dev33.satoken.sso.config.SaSsoServerConfig; 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.name.ApiName; import cn.dev33.satoken.sso.name.ParamName; import cn.dev33.satoken.sso.template.SaSsoServerTemplate; @@ -30,6 +31,8 @@ import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; +import java.util.Map; + /** * SSO 请求处理器 (Server端) * @@ -63,26 +66,26 @@ public class SaSsoServerProcessor { // ------------------ 路由分发 ------------------ - // ---------- SSO-Server端:授权地址 + // sso-server:授权地址 if(req.isPath(apiName.ssoAuth)) { return ssoAuth(); } - // ---------- SSO-Server端:RestAPI 登录接口 + // sso-server:RestAPI 登录接口 if(req.isPath(apiName.ssoDoLogin)) { return ssoDoLogin(); } - // ---------- SSO-Server端:校验ticket 获取账号id - if(req.isPath(apiName.ssoCheckTicket) && cfg.getIsHttp()) { - return ssoCheckTicket(); - } - - // ---------- SSO-Server端:单点注销 + // sso-server:单点注销 if(req.isPath(apiName.ssoSignout)) { return ssoSignout(); } + // sso-server:接收推送消息 + if(req.isPath(apiName.ssoPushS)) { + return ssoPush(); + } + // 默认返回 return SaSsoConsts.NOT_HANDLE; } @@ -162,78 +165,11 @@ public class SaSsoServerProcessor { return cfg.doLoginHandle.apply(req.getParam(paramName.name), req.getParam(paramName.pwd)); } - /** - * SSO-Server端:校验ticket 获取账号id [模式三] - * @return 处理结果 - */ - public Object ssoCheckTicket() { - ParamName paramName = ssoServerTemplate.paramName; - - // 1、获取参数 - SaRequest req = SaHolder.getRequest(); - SaSsoServerConfig ssoServerConfig = ssoServerTemplate.getServerConfig(); - 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); - } - - // 3、校验签名 - if(ssoServerConfig.getIsCheckSign()) { - ssoServerTemplate.getSignTemplate(client).checkRequest(req, - paramName.client, paramName.ticket, paramName.ssoLogoutCall); - } else { - SaSsoManager.printNoCheckSignWarningByRuntime(); - } - - // 4、校验ticket,获取 loginId - Object loginId = ssoServerTemplate.checkTicket(ticket, client); - if(SaFoxUtil.isEmpty(loginId)) { - return SaResult.error("无效ticket:" + ticket); - } - - // 5、注册此客户端的单点注销回调URL - ssoServerTemplate.registerSloCallbackUrl(loginId, client, sloCallback); - - // 6、给 client 端响应结果 - long remainSessionTimeout = ssoServerTemplate.getStpLogic().getSessionTimeoutByLoginId(loginId); - SaResult result = SaResult.data(loginId).set(paramName.remainSessionTimeout, remainSessionTimeout); - result = ssoServerConfig.checkTicketAppendData.apply(loginId, result); - return result; - } - /** * SSO-Server端:单点注销 * @return 处理结果 */ public Object ssoSignout() { - // 获取对象 - SaRequest req = SaHolder.getRequest(); - SaSsoServerConfig cfg = ssoServerTemplate.getServerConfig(); - ParamName paramName = ssoServerTemplate.paramName; - - // SSO-Server端:单点注销 [用户访问式] (不带loginId参数) - if(cfg.getIsSlo() && ! req.hasParam(paramName.loginId)) { - return ssoSignoutByUserVisit(); - } - - // SSO-Server端:单点注销 [Client调用式] (带loginId参数) - if(cfg.getIsSlo() && req.hasParam(paramName.loginId)) { - return ssoSignoutByClientHttp(); - } - - // 默认返回 - return SaSsoConsts.NOT_HANDLE; - } - - /** - * SSO-Server端:单点注销 [用户访问式] - * @return 处理结果 - */ - public Object ssoSignoutByUserVisit() { // 获取对象 SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); @@ -245,43 +181,41 @@ public class SaSsoServerProcessor { } // 完成 - return ssoLogoutBack(req, res); + return SaSsoProcessorHelper.ssoLogoutBack(req, res, ssoServerTemplate.paramName); } /** - * SSO-Server端:单点注销 [Client调用式] + * SSO-Server端:接收推送消息 + * * @return 处理结果 */ - public Object ssoSignoutByClientHttp() { + public Object ssoPush() { ParamName paramName = ssoServerTemplate.paramName; + SaSsoServerConfig ssoServerConfig = ssoServerTemplate.getServerConfig(); - // 获取参数 + // 1、获取参数 SaRequest req = SaHolder.getRequest(); - String loginId = req.getParam(paramName.loginId); String client = req.getParam(paramName.client); - // step.1 校验签名 - if(ssoServerTemplate.getServerConfig().getIsCheckSign()) { - ssoServerTemplate.getSignTemplate(client).checkRequest(req, paramName.client, paramName.loginId); + // 2、校验提供的client是否为非法字符 + if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) { + return SaResult.error("无效 client 标识:" + client); + } + + // 3、校验签名 + Map paramMap = req.getParamMap(); + if(ssoServerConfig.getIsCheckSign()) { + ssoServerTemplate.getSignTemplate(client).checkParamMap(paramMap); } else { SaSsoManager.printNoCheckSignWarningByRuntime(); } - // step.2 单点注销 - ssoServerTemplate.ssoLogout(loginId); - - // 响应 - return SaResult.ok(); + // 处理消息 + SaSsoMessage message = new SaSsoMessage(paramMap); + if( ! ssoServerTemplate.messageHolder.hasHandle(message.getType())) { + return SaResult.error("未能找到消息处理器: " + message.getType()); + } + return ssoServerTemplate.handleMessage(message); } - /** - * 封装:单点注销成功后返回结果 - * @param req SaRequest对象 - * @param res SaResponse对象 - * @return 返回结果 - */ - public Object ssoLogoutBack(SaRequest req, SaResponse res) { - return SaSsoProcessorHelper.ssoLogoutBack(req, res, ssoServerTemplate.paramName); - } - } 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 bbfe80d4..be87ade0 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 @@ -22,11 +22,13 @@ import cn.dev33.satoken.sso.SaSsoManager; import cn.dev33.satoken.sso.config.SaSsoClientConfig; 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.message.handle.client.SaSsoMessageLogoutCallHandle; +import cn.dev33.satoken.sso.util.SaSsoConsts; import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaResult; import java.util.Map; -import java.util.TreeMap; /** * Sa-Token SSO 模板方法类 (Client端) @@ -36,12 +38,8 @@ import java.util.TreeMap; */ public class SaSsoClientTemplate extends SaSsoTemplate { - /** - * 获取底层使用的SsoClient配置对象 - * @return / - */ - public SaSsoClientConfig getClientConfig() { - return SaSsoManager.getClientConfig(); + public SaSsoClientTemplate() { + super.messageHolder.addHandle(new SaSsoMessageLogoutCallHandle()); } @@ -66,7 +64,7 @@ public class SaSsoClientTemplate extends SaSsoTemplate { */ public Object getData(String path, Map paramMap) { String url = buildCustomPathUrl(path, paramMap); - return getClientConfig().sendHttp.apply(url); + return request(url); } // ---------------------- 构建URL ---------------------- @@ -108,56 +106,6 @@ public class SaSsoClientTemplate extends SaSsoTemplate { return SaFoxUtil.joinParam(serverUrl, paramName.redirect, clientLoginUrl); } - /** - * 构建URL:校验ticket的URL - *

在模式三下,Client端拿到Ticket后根据此地址向Server端发送请求,获取账号id - * @param ticket ticket码 - * @param ssoLogoutCallUrl 单点注销时的回调URL - * @return 构建完毕的URL - */ - public String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) { - - SaSsoClientConfig ssoConfig = getClientConfig(); - - // 1、url - String url = ssoConfig.splicingCheckTicketUrl(); - - // 2、参数:client、ticket、ssoLogoutCall - Map paramMap = new TreeMap<>(); - paramMap.put(paramName.ticket, ticket); - paramMap.put(paramName.client, ssoConfig.getClient()); - paramMap.put(paramName.ssoLogoutCall, ssoLogoutCallUrl); - - // 追加签名参数,并序列化为kv字符串 - String signParamStr = getSignTemplate(ssoConfig.getClient()).addSignParamsAndJoin(paramMap); - - // 3、拼接 - return SaFoxUtil.joinParam(url, signParamStr); - } - - /** - * 构建URL:单点注销URL - * @param loginId 要注销的账号id - * @return 单点注销URL - */ - public String buildSloUrl(Object loginId) { - // 获取所需对象 - SaSsoClientConfig ssoConfig = getClientConfig(); - String url = ssoConfig.splicingSloUrl(); - String currClient = ssoConfig.getClient(); - - // 组织请求参数 - Map paramMap = new TreeMap<>(); - paramMap.put(paramName.loginId, loginId); - paramMap.put(paramName.client, currClient); - - // 追加签名参数,并序列化为kv字符串 - String signParamsStr = getSignTemplate(currClient).addSignParamsAndJoin(paramMap); - - // 拼接到 url 上 - return SaFoxUtil.joinParam(url, signParamsStr); - } - /** * 构建URL:Server端 getData 地址,带签名等参数 * @param paramMap 查询参数 @@ -181,7 +129,7 @@ public class SaSsoClientTemplate extends SaSsoTemplate { String url = path; if( ! url.startsWith("http") ) { String serverUrl = ssoConfig.getServerUrl(); - SaSsoException.notEmpty(serverUrl, "请先配置 sa-token.sso.server-url 地址", SaSsoErrorCode.CODE_30012); + SaSsoException.notEmpty(serverUrl, "请先配置 sa-token.sso-client.server-url 地址", SaSsoErrorCode.CODE_30012); url = SaFoxUtil.spliceTwoUrl(serverUrl, path); } @@ -193,6 +141,86 @@ public class SaSsoClientTemplate extends SaSsoTemplate { return SaFoxUtil.joinParam(url, signParamsStr); } + /** + * 构建消息:校验 ticket + * + * @param ticket ticket码 + * @param ssoLogoutCallUrl 单点注销时的回调URL + * @return 构建完毕的URL + */ + public SaSsoMessage buildCheckTicketMessage(String ticket, String ssoLogoutCallUrl) { + SaSsoClientConfig ssoConfig = getClientConfig(); + SaSsoMessage message = new SaSsoMessage(); + message.setType(SaSsoConsts.MESSAGE_CHECK_TICKET); + message.set(paramName.client, ssoConfig.getClient()); + message.set(paramName.ticket, ticket); + message.set(paramName.ssoLogoutCall, ssoLogoutCallUrl); + return message; + } + + /** + * 构建消息:单点注销 + * @param loginId 要注销的账号 id + * @return 单点注销URL + */ + public SaSsoMessage buildSloMessage(Object loginId) { + SaSsoClientConfig ssoConfig = getClientConfig(); + SaSsoMessage message = new SaSsoMessage(); + message.setType(SaSsoConsts.MESSAGE_SIGNOUT); + message.set(paramName.client, ssoConfig.getClient()); + message.set(paramName.loginId, loginId); + return message; + } + + + // ------------------- 消息推送 ------------------- + + /** + * 向 sso-server 推送消息 + * + * @param message / + * @return / + */ + public String pushMessage(SaSsoMessage message) { + SaSsoClientConfig ssoConfig = getClientConfig(); + + // 拼接 push-url 地址 + String pushUrl = ssoConfig.splicingPushUrl(); + SaSsoException.notTrue(! SaFoxUtil.isUrl(pushUrl), "无效 push-url 地址:" + pushUrl, SaSsoErrorCode.CODE_30023); + + // 组织参数 + message.set(paramName.client, ssoConfig.getClient()); + message.checkType(); + String paramsStr = getSignTemplate(ssoConfig.getClient()).addSignParamsAndJoin(message); + + // 发起请求 + String finalUrl = SaFoxUtil.joinParam(pushUrl, paramsStr); + return request(finalUrl); + } + + /** + * 向 sso-server 推送消息,并将返回值转为 SaResult + * + * @param message / + * @return / + */ + public SaResult pushMessageAsSaResult(SaSsoMessage message) { + String res = pushMessage(message); + Map map = SaManager.getSaJsonTemplate().jsonToMap(res); + return new SaResult(map); + } + + + // ------------------- Bean 对象获取 ------------------- + + /** + * 获取底层使用的SsoClient配置对象 + * @return / + */ + public SaSsoClientConfig getClientConfig() { + return SaSsoManager.getClientConfig(); + } + /** * 获取底层使用的 API 签名对象 * @param client 指定客户端标识,填 null 代表获取默认的 @@ -212,18 +240,4 @@ public class SaSsoClientTemplate extends SaSsoTemplate { return new SaSignTemplate(signConfig); } - - // ------------------- 发起请求 ------------------- - - /** - * 发出请求,并返回 SaResult 结果 - * @param url 请求地址 - * @return 返回的结果 - */ - public SaResult request(String url) { - String body = getClientConfig().sendHttp.apply(url); - Map map = SaManager.getSaJsonTemplate().jsonToMap(body); - return new SaResult(map); - } - } 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 f24f5caf..b85e2c61 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 @@ -17,7 +17,6 @@ package cn.dev33.satoken.sso.template; import cn.dev33.satoken.SaManager; import cn.dev33.satoken.config.SaSignConfig; -import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.sign.SaSignTemplate; import cn.dev33.satoken.sso.SaSsoManager; @@ -25,10 +24,14 @@ import cn.dev33.satoken.sso.config.SaSsoClientModel; import cn.dev33.satoken.sso.config.SaSsoServerConfig; 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.message.handle.server.SaSsoMessageCheckTicketHandle; +import cn.dev33.satoken.sso.message.handle.server.SaSsoMessageSignoutHandle; import cn.dev33.satoken.sso.model.SaSsoClientInfo; import cn.dev33.satoken.sso.util.SaSsoConsts; import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.util.SaFoxUtil; +import cn.dev33.satoken.util.SaResult; import java.util.*; @@ -40,12 +43,9 @@ import java.util.*; */ public class SaSsoServerTemplate extends SaSsoTemplate { - /** - * 获取底层使用的SsoServer配置对象 - * @return / - */ - public SaSsoServerConfig getServerConfig() { - return SaSsoManager.getServerConfig(); + public SaSsoServerTemplate() { + super.messageHolder.addHandle(new SaSsoMessageCheckTicketHandle()); + super.messageHolder.addHandle(new SaSsoMessageSignoutHandle()); } // ---------------------- Ticket 操作 ---------------------- @@ -165,7 +165,6 @@ public class SaSsoServerTemplate extends SaSsoTemplate { } // - /** * 根据 账号id 创建一个 Ticket码 * @param loginId 账号id @@ -217,8 +216,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate { } else { // 开始详细比对 if(SaFoxUtil.notEquals(client, ticketClient)) { - throw new SaSsoException("该 ticket 不属于 client=" + client + ", ticket 值: " + ticket) - .setCode(SaSsoErrorCode.CODE_30011); + throw new SaSsoException("该 ticket 不属于 client=" + client + ", ticket 值: " + ticket).setCode(SaSsoErrorCode.CODE_30011); } } @@ -241,19 +239,28 @@ public class SaSsoServerTemplate extends SaSsoTemplate { return SaFoxUtil.getRandomString(64); } + + // ---------------------- Client 信息获取 ---------------------- + /** - * 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) + * 获取所有 Client + * + * @return / + */ + public List getClients() { + Map clients = getServerConfig().getClients(); + return new ArrayList<>(clients.values()); + } + + /** + * 获取应用信息,无效 client 返回 null * * @param client / * @return / */ -// public String getAllowUrl(String client) { -// if(SaFoxUtil.isEmpty(client)) { -// return getServerConfig().getAllowUrl(); -// } else { -// return getClientNotNull(client).getAllowUrl(); -// } -// } + public SaSsoClientModel getClient(String client) { + return getServerConfig().getClients().get(client); + } /** * 获取应用信息,无效 client 则抛出异常 @@ -295,13 +302,70 @@ public class SaSsoServerTemplate extends SaSsoTemplate { } /** - * 获取应用信息,无效 client 返回 null + * 获取所有需要接收消息推送的 Client * - * @param client / * @return / */ - public SaSsoClientModel getClient(String client) { - return getServerConfig().getClients().get(client); + public List getNeedPushClients() { + List list = new ArrayList<>(); + List clients = getClients(); + for(SaSsoClientModel scm : clients) { + if (scm.isValidNoticeUrl()) { + list.add(scm); + } + } + return list; + } + + + // ------------------- 重定向 URL 构建与校验 ------------------- + + /** + * 构建URL:Server端向Client下放ticket的地址 + * @param loginId 账号id + * @param client 客户端标识 + * @param redirect Client端提供的重定向地址 + * @return see note + */ + public String buildRedirectUrl(Object loginId, String client, String redirect) { + + // 校验 重定向地址 是否合法 + checkRedirectUrl(client, redirect); + + // 删掉 旧Ticket + deleteTicket(getTicketValue(loginId)); + + // 创建 新Ticket + String ticket = createTicket(loginId, client); + + // 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket) + return SaFoxUtil.joinParam(encodeBackParam(redirect), paramName.ticket, ticket); + } + + /** + * 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug + * @param url url + * @return 编码过后的url + */ + public String encodeBackParam(String url) { + + // 获取back参数所在位置 + int index = url.indexOf("?" + paramName.back + "="); + if(index == -1) { + index = url.indexOf("&" + paramName.back + "="); + if(index == -1) { + return url; + } + } + + // 开始编码 + int length = paramName.back.length() + 2; + String back = url.substring(index + length); + back = SaFoxUtil.encodeUrl(back); + + // 放回url中 + url = url.substring(0, index + length) + back; + return url; } /** @@ -402,55 +466,11 @@ public class SaSsoServerTemplate extends SaSsoTemplate { } } - // ------------------- SSO ------------------- + + // ------------------- 单点注销 ------------------- /** - * 指定账号单点注销 - * @param loginId 指定账号 - */ - public void ssoLogout(Object loginId) { - - // 如果这个账号尚未登录,则无操作 - SaSession session = getStpLogic().getSessionByLoginId(loginId, false); - if(session == null) { - return; - } - - // step.1 遍历通知 Client 端注销会话 - List scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new); - scmList.forEach(scm -> { - notifyClientLogout(loginId, scm, false); - }); - - // step.2 Server端注销 - getStpLogic().logout(loginId); - } - - /** - * 计算下一个 index 值 - * @param scmList / - * @return / - */ - public 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 - maxIndex++; - return maxIndex; - } - - /** - * 为指定账号id注册单点注销回调信息(模式三) + * 为指定账号 id 注册单点注销回调信息(模式三) * @param loginId 账号id * @param client 指定客户端标识,可为null * @param sloCallbackUrl 单点注销时的回调URL @@ -487,6 +507,52 @@ public class SaSsoServerTemplate extends SaSsoTemplate { session.set(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, scmList); } + /** + * 计算下一个 index 值 + * @param scmList / + * @return / + */ + public 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 + maxIndex++; + return maxIndex; + } + + /** + * 指定账号单点注销 + * @param loginId 指定账号 + */ + public void ssoLogout(Object loginId) { + + // 1、消息推送:单点注销 + pushToAllClientByLogoutCall(loginId); + + // 2、SaSession 挂载的 Client 端注销会话 + SaSession session = getStpLogic().getSessionByLoginId(loginId, false); + if(session == null) { + return; + } + List scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new); + scmList.forEach(scm -> { + notifyClientLogout(loginId, scm, false); + }); + + // 3、Server 端本身注销 + getStpLogic().logout(loginId); + } + /** * 通知指定账号的指定客户端注销 * @param loginId 指定账号 @@ -500,7 +566,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate { return; } - // url + // 如果此 Client 并没有注册 单点登录 回调地址,则立即返回 String sloCallUrl = scm.getSloCallbackUrl(); if(SaFoxUtil.isEmpty(sloCallUrl)) { return; @@ -517,74 +583,102 @@ public class SaSsoServerTemplate extends SaSsoTemplate { String finalUrl = SaFoxUtil.joinParam(sloCallUrl, signParamsStr); // 发起请求 - getServerConfig().sendHttp.apply(finalUrl); + request(finalUrl); } - // ---------------------- 构建URL ---------------------- + + // ------------------- 消息推送 ------------------- /** - * 构建URL:Server端向Client下放ticket的地址 - * @param loginId 账号id - * @param client 客户端标识 - * @param redirect Client端提供的重定向地址 - * @return see note + * 向指定 Client 推送消息 + * @param clientModel / + * @param message / + * @return / */ - public String buildRedirectUrl(Object loginId, String client, String redirect) { - - // 校验 重定向地址 是否合法 - checkRedirectUrl(client, redirect); - - // 删掉 旧Ticket - deleteTicket(getTicketValue(loginId)); - - // 创建 新Ticket - String ticket = createTicket(loginId, client); - - // 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket) - return SaFoxUtil.joinParam(encodeBackParam(redirect), paramName.ticket, ticket); + public String pushMessage(SaSsoClientModel clientModel, SaSsoMessage message) { + message.checkType(); + String noticeUrl = clientModel.splicingNoticeUrl(); + String paramsStr = getSignTemplate(clientModel.getClient()).addSignParamsAndJoin(message); + String finalUrl = SaFoxUtil.joinParam(noticeUrl, paramsStr); + return request(finalUrl); } /** - * 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug - * @param url url - * @return 编码过后的url + * 向指定 client 推送消息,并将返回值转为 SaResult + * + * @param clientModel / + * @param message / + * @return / */ - public String encodeBackParam(String url) { + public SaResult pushMessageAsSaResult(SaSsoClientModel clientModel, SaSsoMessage message) { + String res = pushMessage(clientModel, message); + Map map = SaManager.getSaJsonTemplate().jsonToMap(res); + return new SaResult(map); + } - // 获取back参数所在位置 - int index = url.indexOf("?" + paramName.back + "="); - if(index == -1) { - index = url.indexOf("&" + paramName.back + "="); - if(index == -1) { - return url; + /** + * 向指定 Client 推送消息 + * @param client / + * @param message / + * @return / + */ + public String pushMessage(String client, SaSsoMessage message) { + return pushMessage(getClientNotNull(client), message); + } + + /** + * 向指定 client 推送消息,并将返回值转为 SaResult + * + * @param client / + * @param message / + * @return / + */ + public SaResult pushMessageAsSaResult(String client, SaSsoMessage message) { + String res = pushMessage(client, message); + Map map = SaManager.getSaJsonTemplate().jsonToMap(res); + return new SaResult(map); + } + + /** + * 向所有 Client 推送消息 + * + * @param message / + */ + public void pushToAllClient(SaSsoMessage message) { + List mode3Clients = getNeedPushClients(); + for (SaSsoClientModel client : mode3Clients) { + pushMessage(client, message); + } + } + + /** + * 向所有 Client 推送消息:单点注销回调 + * + * @param loginId / + */ + public void pushToAllClientByLogoutCall(Object loginId) { + 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); + pushMessage(client, message); } } - - // 开始编码 - int length = paramName.back.length() + 2; - String back = url.substring(index + length); - back = SaFoxUtil.encodeUrl(back); - - // 放回url中 - url = url.substring(0, index + length) + back; - return url; } - // ---------------------- 重构 ---------------------- - + // ------------------- Bean 获取 ------------------- /** - * 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除 - * @param ticket Ticket码 - * @param client client 标识 + * 获取底层使用的SsoServer配置对象 + * @return / */ - public void checkTicketVerifySign(SaRequest req, String ticket, String client) { - SaSignTemplate signTemplate = getSignTemplate(client); - signTemplate.checkRequest(req, paramName.client, paramName.ticket, paramName.ssoLogoutCall); + public SaSsoServerConfig getServerConfig() { + return SaSsoManager.getServerConfig(); } - /** * 获取底层使用的 API 签名对象 * @param client 指定客户端标识,填 null 代表获取默认的 @@ -609,8 +703,6 @@ public class SaSsoServerTemplate extends SaSsoTemplate { } - - // ------------------- 返回相应key ------------------- /** diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoTemplate.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoTemplate.java index 39465185..09a8520f 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoTemplate.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoTemplate.java @@ -17,10 +17,15 @@ package cn.dev33.satoken.sso.template; import cn.dev33.satoken.SaManager; import cn.dev33.satoken.sign.SaSignTemplate; +import cn.dev33.satoken.sso.message.SaSsoMessage; +import cn.dev33.satoken.sso.message.SaSsoMessageHolder; import cn.dev33.satoken.sso.name.ApiName; import cn.dev33.satoken.sso.name.ParamName; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; + +import java.util.Map; /** * Sa-Token SSO 模板方法类 (公共端) @@ -78,4 +83,40 @@ public class SaSsoTemplate { return SaManager.getSaSignTemplate(); } + // ----------- 消息处理 + + public SaSsoMessageHolder messageHolder = new SaSsoMessageHolder(); + + /** + * 发送 Http 请求 + * + * @param url / + * @return / + */ + public String request(String url) { + return SaManager.getSaHttpTemplate().get(url); + } + + /** + * 发送 Http 请求,并将响应结果转换为 SaResult + * + * @param url 请求地址 + * @return 返回的结果 + */ + public SaResult requestAsSaResult(String url) { + String body = request(url); + Map map = SaManager.getSaJsonTemplate().jsonToMap(body); + return new SaResult(map); + } + + /** + * 处理指定消息 + * + * @param message / + */ + public Object handleMessage(SaSsoMessage message) { + return messageHolder.handleMessage(this, 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 2ed7a8ad..f0bad5b8 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 @@ -95,16 +95,6 @@ public class SaSsoUtil { return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, client); } -// /** -// * 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) -// * -// * @param client 应用标识 -// * @return / -// */ -// public static String getAllowUrl(String client) { -// return SaSsoServerProcessor.instance.ssoServerTemplate.getAllowUrl(client); -// } - /** * 校验重定向url合法性 * @@ -117,16 +107,6 @@ public class SaSsoUtil { // ------------------- SSO 模式三 ------------------- - - /** - * 构建URL:校验ticket的URL - * @param ticket ticket码 - * @param ssoLogoutCallUrl 单点注销时的回调URL - * @return 构建完毕的URL - */ - public static String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) { - return SaSsoClientProcessor.instance.ssoClientTemplate.buildCheckTicketUrl(ticket, ssoLogoutCallUrl); - } /** * 为指定账号id注册单点注销回调URL @@ -138,15 +118,6 @@ public class SaSsoUtil { SaSsoServerProcessor.instance.ssoServerTemplate.registerSloCallbackUrl(loginId, client, sloCallbackUrl); } - /** - * 构建URL:单点注销URL - * @param loginId 要注销的账号id - * @return 单点注销URL - */ - public static String buildSloUrl(Object loginId) { - return SaSsoClientProcessor.instance.ssoClientTemplate.buildSloUrl(loginId); - } - /** * 指定账号单点注销 (以Server方发起) * @param loginId 指定账号 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 8eea64f4..1ee4b4d9 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 @@ -55,6 +55,18 @@ public class SaSsoConsts { /** SSO 模式3 */ public static final int SSO_MODE_3 = 3; +// /** 消息类型:单点注销 */ +// public static final String MESSAGE_LOGOUT = "logout"; + + /** 消息类型:校验 ticket */ + public static final String MESSAGE_CHECK_TICKET = "checkTicket"; + + /** 消息类型:单点注销 */ + public static final String MESSAGE_SIGNOUT = "signout"; + + /** 消息类型:单点注销回调 */ + public static final String MESSAGE_LOGOUT_CALL = "logoutCall"; + }