diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaJsonConvertException.java b/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaJsonConvertException.java new file mode 100644 index 00000000..d3ffa747 --- /dev/null +++ b/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaJsonConvertException.java @@ -0,0 +1,22 @@ +package cn.dev33.satoken.exception; + +/** + * 一个异常:代表 JSON 转换失败 + * + * @author kong + */ +public class SaJsonConvertException extends SaTokenException { + + /** + * 序列化版本号 + */ + private static final long serialVersionUID = 6806129545290134144L; + + /** + * 一个异常:代表 JSON 转换失败 + */ + public SaJsonConvertException(Throwable e) { + super(e); + } + +} diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaResult.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaResult.java index d751215b..614ddb20 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaResult.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaResult.java @@ -21,11 +21,33 @@ public class SaResult extends LinkedHashMap implements Serializa public static final int CODE_SUCCESS = 200; public static final int CODE_ERROR = 500; + /** + * 构建 + */ + public SaResult() { + } + + /** + * 构建 + * @param code 状态码 + * @param msg 信息 + * @param data 数据 + */ public SaResult(int code, String msg, Object data) { this.setCode(code); this.setMsg(msg); this.setData(data); } + + /** + * 根据 Map 快速构建 + * @param map / + */ + public SaResult(Map map) { + for (String key: map.keySet()) { + this.set(key, map.get(key)); + } + } /** * 获取code @@ -138,9 +160,16 @@ public class SaResult extends LinkedHashMap implements Serializa public String toString() { return "{" + "\"code\": " + this.getCode() - + ", \"msg\": \"" + this.getMsg() + "\"" - + ", \"data\": \"" + this.getData() + "\"" + + ", \"msg\": " + transValue(this.getMsg()) + + ", \"data\": " + transValue(this.getData()) + "}"; } + private String transValue(Object value) { + if(value instanceof String) { + return "\"" + value + "\""; + } + return String.valueOf(value); + } + } diff --git a/sa-token-doc/doc/sso/sso-apidoc.md b/sa-token-doc/doc/sso/sso-apidoc.md index df6dd035..e1acc5e7 100644 --- a/sa-token-doc/doc/sso/sso-apidoc.md +++ b/sa-token-doc/doc/sso/sso-apidoc.md @@ -37,8 +37,9 @@ http://{host}:{port}/sso/doLogin | name | 是 | 用户名 | | pwd | 是 | 密码 | -此接口属于 RestAPI (使用ajax访问),会进入后端配置的 `setDoLoginHandle` 函数中,另外需要注意: -此接口并非只能携带 name、pwd 参数,因为你可以在 setDoLoginHandle 函数里通过 `SaHolder.getRequest().getParam("xxx")` 来获取其它参数。 +此接口属于 RestAPI (使用ajax访问),会进入后端配置的 `sso.setDoLoginHandle` 函数中,此函数的返回值即是此接口的响应值。 + +另外需要注意:此接口并非只能携带 name、pwd 参数,因为你可以在方法里通过 `SaHolder.getRequest().getParam("xxx")` 来获取前端提交的其它参数。 ### 3、Ticket 校验接口 @@ -52,12 +53,29 @@ http://{host}:{port}/sso/checkTicket | 参数 | 是否必填 | 说明 | | :-------- | :-------- | :-------- | -| ticket | 是 | 在步骤 5.1 中授权重定向时的 ticket 参数 | +| ticket | 是 | 在步骤 1 中授权重定向时的 ticket 参数 | | ssoLogoutCall | 否 | 单点注销时的回调通知地址,只在SSO模式三单点注销时需要携带此参数| 返回值场景: -- 返回空,代表校验失败。 -- 返回具体的 loginId,例如10001,代表校验成功,值为此 ticket 码代表的用户id。 +- 校验成功时: + +``` js +{ + "code": 200, + "msg": "ok", + "data": "10001" // 此 ticket 指向的 loginId +} +``` + +- 校验失败时: + +``` js +{ + "code": 500, + "msg": "无效ticket:vESj0MtqrtSoucz4DDHJnsqU3u7AKFzbj0KH57EfJvuhkX1uAH23DuNrMYSjTnEq", + "data": null +} +``` ### 4、单点注销接口 @@ -90,6 +108,17 @@ http://{host}:{port}/sso/logout } ``` +如果单点注销失败,将返回: + +``` js +{ + "code": 500, // 200表示请求成功,非200标识请求失败 + "msg": "无效秘钥:xxx", // 失败原因 + "data": null +} +``` + +
SSO 认证中心只有这四个接口,接下来让我一起来看一下 Client 端的对接流程:[SSO模式一 共享Cookie同步会话](/sso/sso-type1) diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/config/SaSsoConfig.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/config/SaSsoConfig.java index ea74ae19..c935401e 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/config/SaSsoConfig.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/config/SaSsoConfig.java @@ -279,12 +279,16 @@ public class SaSsoConfig implements Serializable { /** * SSO-Server端:未登录时返回的View */ - public Supplier notLoginView = () -> "当前会话在SSO-Server认证中心尚未登录"; + public Supplier notLoginView = () -> { + return "当前会话在SSO-Server认证中心尚未登录"; + }; /** * SSO-Server端:登录函数 */ - public BiFunction doLoginHandle = (name, pwd) -> SaResult.error(); + public BiFunction doLoginHandle = (name, pwd) -> { + return SaResult.error(); + }; /** * SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用) @@ -294,7 +298,9 @@ public class SaSsoConfig implements Serializable { /** * SSO-Client端:发送Http请求的处理函数 */ - public Function sendHttp = url -> {throw new SaTokenException("请配置Http处理器");}; + public Function sendHttp = url -> { + throw new SaTokenException("请配置 Http 请求处理器"); + }; /** @@ -349,7 +355,7 @@ public class SaSsoConfig implements Serializable { * @param sendHttp SSO-Client端:发送Http请求的处理函数 * @return 对象自身 */ - public SaSsoConfig setSendHttp(Function sendHttp) { + public SaSsoConfig setSendHttp(Function sendHttp) { this.sendHttp = sendHttp; return this; } @@ -357,7 +363,7 @@ public class SaSsoConfig implements Serializable { /** * @return 函数 SSO-Client端:发送Http请求的处理函数 */ - public Function getSendHttp() { + public Function getSendHttp() { return sendHttp; } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java index 8d116702..ba2cdc8e 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java @@ -1,5 +1,8 @@ package cn.dev33.satoken.sso; +import java.util.Map; + +import cn.dev33.satoken.SaManager; import cn.dev33.satoken.config.SaSsoConfig; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.model.SaRequest; @@ -19,6 +22,8 @@ import cn.dev33.satoken.util.SaResult; */ public class SaSsoHandle { + // ----------- SSO-Server 端路由分发 + /** * 处理Server端所有请求 * @return 处理结果 @@ -46,14 +51,14 @@ public class SaSsoHandle { return ssoCheckTicket(); } - // SSO-Server端:单点注销 [模式一] (不带loginId参数) + // SSO-Server端:单点注销 [用户访问式] (不带loginId参数) if(req.isPath(Api.ssoLogout) && cfg.getIsSlo() && req.hasParam(ParamName.loginId) == false) { - return ssoServerLogoutType1(); + return ssoLogoutByUserVisit(); } - // SSO-Server端:单点注销 [模式三] (带loginId参数) + // SSO-Server端:单点注销 [Client调用式] (带loginId参数 & isHttp=true) if(req.isPath(Api.ssoLogout) && cfg.getIsHttp() && cfg.getIsSlo() && req.hasParam(ParamName.loginId)) { - return ssoServerLogout(); + return ssoLogoutByClientHttp(); } // 默认返回 @@ -116,38 +121,47 @@ public class SaSsoHandle { String ticket = req.getParam(ParamName.ticket); String sloCallback = req.getParam(ParamName.ssoLogoutCall); - // 校验ticket,获取对应的账号id + // 校验ticket,获取 loginId Object loginId = SaSsoUtil.checkTicket(ticket); - + // 注册此客户端的单点注销回调URL SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback); - // 返回给Client端 - return loginId; + // 给 client 端响应结果 + if(SaFoxUtil.isEmpty(loginId)) { + return SaResult.error("无效ticket:" + ticket); + } else { + return SaResult.data(loginId); + } } /** - * SSO-Server端:单点注销 [模式一] + * SSO-Server端:单点注销 [用户访问式] * @return 处理结果 */ - public static Object ssoServerLogoutType1() { + public static Object ssoLogoutByUserVisit() { // 获取对象 SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); + SaSsoConfig cfg = SaSsoManager.getConfig(); StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; + String loginId = req.getParam(ParamName.loginId); + + // step.1 遍历 Client 端注销 + SaSsoUtil.forEachSloUrl(loginId, url -> cfg.getSendHttp().apply(url)); - // 开始处理 + // step.2 Server 端注销 stpLogic.logout(); - // 返回 + // 完成 return ssoLogoutBack(req, res); } /** - * SSO-Server端:单点注销 [模式三] + * SSO-Server端:单点注销 [Client调用式] * @return 处理结果 */ - public static Object ssoServerLogout() { + public static Object ssoLogoutByClientHttp() { // 获取对象 SaRequest req = SaHolder.getRequest(); SaSsoConfig cfg = SaSsoManager.getConfig(); @@ -157,21 +171,22 @@ public class SaSsoHandle { String loginId = req.getParam(ParamName.loginId); String secretkey = req.getParam(ParamName.secretkey); - // 遍历通知Client端注销会话 // step.1 校验秘钥 SaSsoUtil.checkSecretkey(secretkey); - // step.2 遍历通知Client端注销会话 + // step.2 遍历 Client 端注销 SaSsoUtil.forEachSloUrl(loginId, url -> cfg.getSendHttp().apply(url)); - // step.3 Server端注销 + // step.3 Server 端注销 stpLogic.logout(loginId); // 完成 - return SaSsoConsts.OK; + return SaResult.ok(); } + // ----------- SSO-Client 端路由分发 + /** * 处理Client端所有请求 * @return 处理结果 @@ -236,20 +251,21 @@ public class SaSsoHandle { String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back); return res.redirect(serverAuthUrl); } else { - // ------- 1、校验ticket,获取账号id + // ------- 1、校验ticket,获取 loginId Object loginId = checkTicket(ticket, Api.ssoLogin); // Be: 如果开发者自定义了处理逻辑 if(cfg.getTicketResultHandle() != null) { return cfg.getTicketResultHandle().apply(loginId, back); } - // ------- 2、如果loginId有值,说明ticket有效,进行登录并重定向至back地址 - if(loginId != null ) { + + // ------- 2、如果 loginId 无值,说明 ticket 无效 + if(SaFoxUtil.isEmpty(loginId)) { + throw new SaSsoException("无效ticket:" + ticket).setCode(SaSsoExceptionCode.CODE_20004); + } else { + // 3、如果 loginId 有值,说明 ticket 有效,此时进行登录并重定向至back地址 stpLogic.login(loginId); return res.redirect(back); - } else { - // 如果ticket无效: - throw new SaSsoException("无效ticket:" + ticket).setCode(SaSsoExceptionCode.CODE_20004); } } } @@ -279,7 +295,6 @@ public class SaSsoHandle { // 获取对象 SaRequest req = SaHolder.getRequest(); SaResponse res = SaHolder.getResponse(); - SaSsoConfig cfg = SaSsoManager.getConfig(); StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; // 如果未登录,则无需注销 @@ -289,17 +304,19 @@ public class SaSsoHandle { // 调用SSO-Server认证中心API,进行注销 String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId()); - String body = String.valueOf(cfg.getSendHttp().apply(url)); - if(SaSsoConsts.OK.equals(body) == false) { - return SaResult.error("单点注销失败"); - } + SaResult result = request(url); - // 返回 - return ssoLogoutBack(req, res); + // 校验 + if(result.getCode() == SaResult.CODE_SUCCESS) { + return ssoLogoutBack(req, res); + } else { + // 将 sso-server 回应的消息作为异常抛出 + throw new SaSsoException(result.getMsg()).setCode(SaSsoExceptionCode.CODE_20006); + } } /** - * SSO-Client端:单点注销的回调 [模式三] + * SSO-Client端:单点注销的回调 [模式三] * @return 处理结果 */ public static Object ssoLogoutCall() { @@ -311,11 +328,17 @@ public class SaSsoHandle { String loginId = req.getParam(ParamName.loginId); String secretkey = req.getParam(ParamName.secretkey); + // 注销当前应用端会话 SaSsoUtil.checkSecretkey(secretkey); stpLogic.logout(loginId); - return SaSsoConsts.OK; + + // 响应 + return SaResult.ok("单点注销回调成功"); } + + // ----------- 工具方法 + /** * 封装:单点注销成功后返回结果 * @param req SaRequest对象 @@ -348,20 +371,41 @@ public class SaSsoHandle { */ public static Object checkTicket(String ticket, String currUri) { SaSsoConfig cfg = SaSsoManager.getConfig(); + // --------- 两种模式 if(cfg.getIsHttp()) { - // 模式三:使用http请求校验ticket + // 模式三:使用 http 请求从认证中心校验ticket String ssoLogoutCall = null; if(cfg.getIsSlo()) { ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, Api.ssoLogoutCall); } + + // 发起请求 String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall); - Object body = cfg.getSendHttp().apply(checkUrl); - return (SaFoxUtil.isEmpty(body) ? null : body); + SaResult result = request(checkUrl); + + // 校验 + if(result.getCode() == SaResult.CODE_SUCCESS) { + return result.getData(); + } else { + // 将 sso-server 回应的消息作为异常抛出 + throw new SaSsoException(result.getMsg()).setCode(SaSsoExceptionCode.CODE_20005); + } } else { // 模式二:直连Redis校验ticket return SaSsoUtil.checkTicket(ticket); } } + /** + * 发出请求,并返回 SaResult 结果 + * @param url 请求地址 + * @return 返回的结果 + */ + public static SaResult request(String url) { + String body = SaSsoManager.getConfig().getSendHttp().apply(url); + Map map = SaManager.getSaJsonTemplate().parseJsonToMap(body); + return new SaResult(map); + } + } diff --git a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoExceptionCode.java b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoExceptionCode.java index 2e47de04..cfd1afba 100644 --- a/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoExceptionCode.java +++ b/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/exception/SaSsoExceptionCode.java @@ -19,7 +19,11 @@ public class SaSsoExceptionCode { /** 提供的 ticket 是无效的 */ public static final int CODE_20004 = 20004; - - + + /** 在模式三下,sso-client 调用 sso-server 端 校验ticket接口 时,得到的响应是校验失败 */ + public static final int CODE_20005 = 20005; + + /** 在模式三下,sso-client 调用 sso-server 端 单点注销接口 时,得到的响应是注销失败 */ + public static final int CODE_20006 = 20006; } diff --git a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/json/SaJsonTemplateForJackson.java b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/json/SaJsonTemplateForJackson.java index 3e3615fa..efb736e1 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/json/SaJsonTemplateForJackson.java +++ b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/spring/json/SaJsonTemplateForJackson.java @@ -5,7 +5,7 @@ import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.exception.SaJsonConvertException; import cn.dev33.satoken.json.SaJsonTemplate; /** @@ -31,7 +31,7 @@ public class SaJsonTemplateForJackson implements SaJsonTemplate { try { return objectMapper.writeValueAsString(obj); } catch (JsonProcessingException e) { - throw new SaTokenException(e); + throw new SaJsonConvertException(e); } } @@ -45,7 +45,7 @@ public class SaJsonTemplateForJackson implements SaJsonTemplate { Map map = objectMapper.readValue(jsonStr, Map.class); return map; } catch (JsonProcessingException e) { - throw new SaTokenException(e); + throw new SaJsonConvertException(e); } } diff --git a/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/json/SaJsonTemplateForJackson.java b/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/json/SaJsonTemplateForJackson.java index 78b6766f..5f58efa2 100644 --- a/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/json/SaJsonTemplateForJackson.java +++ b/sa-token-starter/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/json/SaJsonTemplateForJackson.java @@ -5,7 +5,7 @@ import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.exception.SaJsonConvertException; import cn.dev33.satoken.json.SaJsonTemplate; /** @@ -31,7 +31,7 @@ public class SaJsonTemplateForJackson implements SaJsonTemplate { try { return objectMapper.writeValueAsString(obj); } catch (JsonProcessingException e) { - throw new SaTokenException(e); + throw new SaJsonConvertException(e); } } @@ -45,7 +45,7 @@ public class SaJsonTemplateForJackson implements SaJsonTemplate { Map map = objectMapper.readValue(jsonStr, Map.class); return map; } catch (JsonProcessingException e) { - throw new SaTokenException(e); + throw new SaJsonConvertException(e); } }