From c0f781a9fbbdd31d64564f6afd7a3a209d82e103 Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Wed, 1 Apr 2026 05:25:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(sso):=20=E8=A1=A5=E5=85=A8=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E7=89=88=20SSO=20NoSdk=20=E6=A8=A1=E5=BC=8F=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 12 + .../com/pj/SaSsoClientNoSdkApplication.java | 3 - .../java/com/pj/sso/SsoClientController.java | 236 +++++++++--------- .../main/java/com/pj/sso/SsoRequestUtil.java | 115 ++------- .../src/main/java/com/pj/sso/SsoSignUtil.java | 102 ++++++++ sa-token-doc/sso/sso-nosdk.md | 20 +- 6 files changed, 270 insertions(+), 218 deletions(-) create mode 100644 sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoSignUtil.java 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 cbae7ecb..5c5a1512 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 @@ -47,6 +47,18 @@ sa-token: push-url: http://sa-sso-client1.com:9003/sso/pushC # 接口调用秘钥 (如果不配置则使用全局默认秘钥) secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor + # 应用 sso-client3-nosdk:采用 NoSdk 模式对接 (不依赖 Sa-Token 客户端 SDK,手动实现协议) + sso-client3-nosdk: + # 应用名称 + client: sso-client3-nosdk + # 允许授权地址 + allow-url: "*" + # 是否接收消息推送 + is-push: true + # 消息推送地址 + push-url: http://sa-sso-client1.com:9004/sso/pushC + # 接口调用秘钥 (如果不配置则使用全局默认秘钥) + secret-key: SSO-C3-NoSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor # 应用 sso-client3-resdk:采用 ReSdk 模式对接 sso-client3-resdk: # 应用名称 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/SaSsoClientNoSdkApplication.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/SaSsoClientNoSdkApplication.java index ed3dd2ec..a5d67798 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/SaSsoClientNoSdkApplication.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/SaSsoClientNoSdkApplication.java @@ -16,9 +16,6 @@ public class SaSsoClientNoSdkApplication { System.out.println("测试访问应用端三: http://sa-sso-client3.com:9004"); System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456"); System.out.println(); - - System.err.println("自 v1.43.0 版本起,Sa-Token SSO 不再维护 NoSdk 示例,此项目仅做留档"); - System.err.println("如您需要非 Sa-Token 技术栈项目接入 SSO-Server 认证中心,请参考 ReSdk 版本示例"); } } \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoClientController.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoClientController.java index 15098acc..f9f329a5 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoClientController.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoClientController.java @@ -1,22 +1,22 @@ package com.pj.sso; -import java.io.IOException; -import java.util.Objects; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - +import com.pj.sso.util.AjaxJson; +import com.pj.sso.util.MyHttpSessionHolder; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import com.pj.sso.util.AjaxJson; -import com.pj.sso.util.MyHttpSessionHolder; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; /** - * SSO Client端 Controller + * SSO Client端 Controller * @author click33 */ @RestController @@ -25,162 +25,166 @@ public class SsoClientController { // SSO-Client端:首页 @RequestMapping("/") public String index(HttpSession session) { - String str = "

Sa-Token SSO-Client 应用端

" + - "

当前会话登录账号:" + session.getAttribute("userId") + "

" + - "

登录" + - " 注销" + + Object userId = session.getAttribute("userId"); + String str = "

Sa-Token SSO-Client 应用端 (模式三-NoSdk)

" + + "

当前会话是否登录:" + (userId != null) + " (" + userId + ")

" + + "

登录" + + " 注销" + " 获取资料

"; return str; } - // SSO-Client端:单点登录地址 + // SSO-Client端:单点登录地址 @RequestMapping("/sso/login") - public Object ssoLogin(String ticket, @RequestParam(defaultValue = "/") String back, + public Object ssoLogin(String ticket, @RequestParam(defaultValue = "/") String back, HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException { - - // 如果已经登录,则直接返回 - if(session.getAttribute("userId") != null) { + + // 如果已经登录,则直接返回 + if (session.getAttribute("userId") != null) { response.sendRedirect(back); return null; } - + /* - * 此时有两种情况: - * 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心 - * 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录 + * 此时有两种情况: + * 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心 + * 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录 */ - if(ticket == null) { - String currUrl = request.getRequestURL().toString(); - String clientLoginUrl = currUrl + "?back=" + SsoRequestUtil.encodeUrl(back); - String serverAuthUrl = SsoRequestUtil.authUrl + "?redirect=" + clientLoginUrl; + if (ticket == null) { + // ------- 情况 1 + // 当前 url,形如:http://sso-client.com/sso/login?back=xxx + String clientLoginUrl = request.getRequestURL().toString() + "?back=" + SsoRequestUtil.encodeUrl(back); + // 最终授权地址,形如:http://sso-server.com/sso/auth?client=xxx&redirect=http://sso-client.com/sso/login?back=xxx + String serverAuthUrl = SsoRequestUtil.authUrl + + "?client=" + SsoRequestUtil.clientId + + "&redirect=" + clientLoginUrl; response.sendRedirect(serverAuthUrl); return null; + } else { - // 获取当前 client 端的单点注销回调地址 - String ssoLogoutCall = ""; - if(SsoRequestUtil.isSlo) { - ssoLogoutCall = request.getRequestURL().toString().replace("/sso/login", "/sso/logoutCall"); - } - - // 校验 ticket - String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳 - String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串 - String sign = SsoRequestUtil.getSignByTicket(ticket, ssoLogoutCall, timestamp, nonce); // 参数签名 - String checkUrl = SsoRequestUtil.checkTicketUrl + - "?timestamp=" + timestamp + - "&nonce=" + nonce + - "&sign=" + sign + - "&ticket=" + ticket + - "&ssoLogoutCall=" + ssoLogoutCall; - AjaxJson result = SsoRequestUtil.request(checkUrl); - - // 200 代表校验成功 - if(result.getCode() == 200 && SsoRequestUtil.isEmpty(result.getData()) == false) { - // 登录上 - Object loginId = result.getData(); - session.setAttribute("userId", loginId); + // ------- 情况 2 + // 构建 checkTicket 请求参数,以 ticket 查询 userId + Map params = new LinkedHashMap<>(); + params.put("msgType", "checkTicket"); + params.put("client", SsoRequestUtil.clientId); + params.put("ticket", ticket); + SsoSignUtil.addSignParams(params); + String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params); + AjaxJson result = SsoRequestUtil.request(pushUrl); + + // 200 代表校验成功 + if (result.getCode() == 200 && !SsoRequestUtil.isEmpty(result.getData())) { + // 登录上 + session.setAttribute("userId", result.getData()); // 返回 back 地址 response.sendRedirect(back); return null; - } else { - // 将 sso-server 回应的消息作为异常抛出 + // 将 sso-server 回应的消息作为异常抛出 throw new RuntimeException(result.getMsg()); } } } - + // SSO-Client端:单点注销地址 @RequestMapping("/sso/logout") - public Object ssoLogout(@RequestParam(defaultValue = "/") String back, + public Object ssoLogout(@RequestParam(defaultValue = "/") String back, HttpServletResponse response, HttpSession session) throws IOException { - - // 如果未登录,则无需注销 - if(session.getAttribute("userId") == null) { + + // 如果未登录,则无需注销 + if (session.getAttribute("userId") == null) { response.sendRedirect(back); return null; - } - - // 调用 sso-server 认证中心单点注销API - Object loginId = session.getAttribute("userId"); // 账号id - String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳 - String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串 - String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce); // 参数签名 - - String url = SsoRequestUtil.sloUrl + - "?loginId=" + loginId + - "×tamp=" + timestamp + - "&nonce=" + nonce + - "&sign=" + sign; - AjaxJson result = SsoRequestUtil.request(url); - - // 校验响应状态码,200 代表成功 - if(result.getCode() == 200) { - - // 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀 + } + + // 调用 sso-server 认证中心单点注销 API + Object loginId = session.getAttribute("userId"); + Map params = new LinkedHashMap<>(); + params.put("msgType", "signout"); + params.put("client", SsoRequestUtil.clientId); + params.put("loginId", String.valueOf(loginId)); + SsoSignUtil.addSignParams(params); + String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params); + AjaxJson result = SsoRequestUtil.request(pushUrl); + + // 校验响应状态码,200 代表成功 + if (result.getCode() == 200) { + // 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀 session.removeAttribute("userId"); // 返回 back 地址 response.sendRedirect(back); return null; - } else { - // 将 sso-server 回应的消息作为异常抛出 + // 将 sso-server 回应的消息作为异常抛出 throw new RuntimeException(result.getMsg()); } } - - // SSO-Client端:单点注销回调地址 - @RequestMapping("/sso/logoutCall") - public Object ssoLogoutCall(String loginId, String autoLogout, String timestamp, String nonce, String sign) { - - // 校验签名 - String calcSign = SsoRequestUtil.getSignByLogoutCall(loginId, autoLogout, timestamp, nonce); - if(calcSign.equals(sign) == false) { - System.out.println("无效签名,拒绝应答:" + sign); - return AjaxJson.getError("无效签名,拒绝应答" + sign); + + // SSO-Server 端消息推送接收地址(单点注销回调等) + @RequestMapping("/sso/pushC") + public Object ssoPushC(HttpServletRequest request) { + + // 将请求参数收集为 Map + Map params = new LinkedHashMap<>(); + for (Map.Entry entry : request.getParameterMap().entrySet()) { + params.put(entry.getKey(), entry.getValue()[0]); } - - // 注销这个账号id - for (HttpSession session: MyHttpSessionHolder.sessionList) { - Object userId = session.getAttribute("userId"); - if(Objects.equals(String.valueOf(userId), loginId)) { - session.removeAttribute("userId"); + + // 校验签名 + if (!SsoSignUtil.verifySign(params)) { + return AjaxJson.getError("无效签名,拒绝应答"); + } + + // 按 msgType 分发处理 + String msgType = params.get("msgType"); + + // 单点注销回调 + if ("logoutCall".equals(msgType)) { + // 注销这个账号 id 在本 client 端的所有会话 + String loginId = params.get("loginId"); + for (HttpSession session : MyHttpSessionHolder.sessionList) { + Object userId = session.getAttribute("userId"); + if (Objects.equals(String.valueOf(userId), loginId)) { + session.removeAttribute("userId"); + } } + return AjaxJson.getSuccess("账号id=" + loginId + " 注销成功"); } - - return AjaxJson.getSuccess("账号id=" + loginId + " 注销成功"); + + // 其它消息类型 +// if("xxx".equals(msgType)) { +// // 处理 xxx 消息 +// } + + return AjaxJson.getError("未知消息类型:" + msgType); } - // 查询我的账号信息 (调用此接口的前提是 sso-server 端开放了 /sso/userinfo 路由) + // 查询我的账号信息(调用 sso-server 端 userinfo 消息处理器) @RequestMapping("/sso/myInfo") public Object myInfo(HttpSession session) { - // 如果尚未登录 - if(session.getAttribute("userId") == null) { + // 如果尚未登录 + if (session.getAttribute("userId") == null) { return "尚未登录,无法获取"; } - // 组织 url 参数 - Object loginId = session.getAttribute("userId"); // 账号id - String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳 - String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串 - String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce); // 参数签名 - - String url = SsoRequestUtil.getDataUrl + - "?loginId=" + loginId + - "×tamp=" + timestamp + - "&nonce=" + nonce + - "&sign=" + sign; - AjaxJson result = SsoRequestUtil.request(url); - - // 返回给前端 + Object loginId = session.getAttribute("userId"); + Map params = new LinkedHashMap<>(); + params.put("msgType", "userinfo"); + params.put("client", SsoRequestUtil.clientId); + params.put("loginId", String.valueOf(loginId)); + SsoSignUtil.addSignParams(params); + String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params); + AjaxJson result = SsoRequestUtil.request(pushUrl); + + // 返回给前端 return result; } - // 全局异常拦截 + // 全局异常拦截 @ExceptionHandler public AjaxJson handlerException(Exception e) { - e.printStackTrace(); + e.printStackTrace(); return AjaxJson.getError(e.getMessage()); } - + } diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java index 0fc8c3ee..3a7a22ae 100644 --- a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoRequestUtil.java @@ -5,61 +5,47 @@ import com.pj.sso.util.AjaxJson; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.security.MessageDigest; import java.util.Map; -import java.util.Random; /** - * 封装一些 sso 共用方法 - * + * 封装一些 sso 共用方法 + * * @author click33 * @since 2022-4-30 */ public class SsoRequestUtil { /** - * SSO-Server端主机地址 + * SSO-Server 端主机地址 */ public static String serverUrl = "http://sa-sso-server.com:9000"; /** - * SSO-Server端 统一认证地址 + * SSO-Server 端统一认证地址 */ public static String authUrl = serverUrl + "/sso/auth"; /** - * SSO-Server端 ticket校验地址 + * SSO-Server 端统一消息推送地址(ticket校验、单点注销、获取用户信息等均通过此入口) */ - public static String checkTicketUrl = serverUrl + "/sso/checkTicket"; + public static String pushSUrl = serverUrl + "/sso/pushS"; /** - * 单点注销地址 + * 当前应用的客户端标识(需与 sso-server 端 clients 配置一致) */ - public static String sloUrl = serverUrl + "/sso/signout"; + public static String clientId = "sso-client3-nosdk"; /** - * SSO-Server端 查询userinfo地址 + * 接口调用秘钥(需与 sso-server 端对应 client 配置一致) */ - public static String getDataUrl = serverUrl + "/sso/getData"; + public static String secretKey = "SSO-C3-NoSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor"; + + // -------------------------- 工具方法 /** - * 打开单点注销功能 - */ - public static boolean isSlo = true; - - /** - * 接口调用秘钥 - */ - public static String secretKey = "kQwIOrYvnXmSDkwEiFngrKidMcdrgKor"; - - - - // -------------------------- 工具方法 - - /** - * 发出请求,并返回 SaResult 结果 - * @param url 请求地址 - * @return 返回的结果 + * 发出请求,并返回 AjaxJson 结果 + * @param url 请求地址(含查询参数) + * @return 返回的结果 */ public static AjaxJson request(String url) { Map map = Forest.post(url).executeAsMap(); @@ -67,75 +53,28 @@ public class SsoRequestUtil { } /** - * 根据参数计算签名 - * @param loginId 账号id - * @param timestamp 当前时间戳,13位 - * @param nonce 随机字符串 - * @return 签名 + * 将参数 Map 拼接到 baseUrl 后面(值进行 URL 编码),返回完整 URL + * @param baseUrl 基础 URL + * @param params 请求参数 + * @return 拼接后的完整 URL */ - public static String getSign(Object loginId, String timestamp, String nonce) { - return md5("loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretKey); - } - // 单点注销回调时构建签名 - public static String getSignByLogoutCall(Object loginId, String autoLogout, String timestamp, String nonce) { - return md5("autoLogout=" + autoLogout + "&loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretKey); - } - // 校验ticket 时构建签名 - public static String getSignByTicket(String ticket, String ssoLogoutCall, String timestamp, String nonce) { - return md5("nonce=" + nonce + "&ssoLogoutCall=" + ssoLogoutCall + "&ticket=" + ticket + "×tamp=" + timestamp + "&key=" + secretKey); + public static String buildUrl(String baseUrl, Map params) { + StringBuilder sb = new StringBuilder(baseUrl).append("?"); + for (Map.Entry entry : params.entrySet()) { + sb.append(entry.getKey()).append("=").append(encodeUrl(entry.getValue())).append("&"); + } + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); } /** * 指定元素是否为null或者空字符串 - * @param str 指定元素 + * @param str 指定元素 * @return 是否为null或者空字符串 */ public static boolean isEmpty(Object str) { return str == null || "".equals(str); } - - /** - * md5加密 - * @param str 指定字符串 - * @return 加密后的字符串 - */ - public static String md5(String str) { - str = (str == null ? "" : str); - char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - try { - byte[] btInput = str.getBytes(); - MessageDigest mdInst = MessageDigest.getInstance("MD5"); - mdInst.update(btInput); - byte[] md = mdInst.digest(); - int j = md.length; - char[] strA = new char[j * 2]; - int k = 0; - for (byte byte0 : md) { - strA[k++] = hexDigits[byte0 >>> 4 & 0xf]; - strA[k++] = hexDigits[byte0 & 0xf]; - } - return new String(strA); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * 生成指定长度的随机字符串 - * - * @param length 字符串的长度 - * @return 一个随机字符串 - */ - public static String getRandomString(int length) { - String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - Random random = new Random(); - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < length; i++) { - int number = random.nextInt(62); - sb.append(str.charAt(number)); - } - return sb.toString(); - } /** * URL编码 diff --git a/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoSignUtil.java b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoSignUtil.java new file mode 100644 index 00000000..188b0807 --- /dev/null +++ b/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk/src/main/java/com/pj/sso/SsoSignUtil.java @@ -0,0 +1,102 @@ +package com.pj.sso; + +import java.security.MessageDigest; +import java.util.Map; +import java.util.Random; +import java.util.TreeMap; + +/** + * SSO 签名工具类:签名生成、注入与校验 + * + * @author click33 + */ +public class SsoSignUtil { + + + + // -------------------------- 签名方法 + + /** + * 计算签名:将 params(排除 sign 字段)按 key 字典序升序排列, + * 拼接为 k=v&k=v 后追加 &key={secretKey},整体 MD5 + * @param params 请求参数(不含 sign) + * @return 签名值 + */ + public static String computeSign(Map params) { + TreeMap sorted = new TreeMap<>(params); + sorted.remove("sign"); + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : sorted.entrySet()) { + if (sb.length() > 0) sb.append("&"); + sb.append(entry.getKey()).append("=").append(entry.getValue()); + } + sb.append("&key=").append(SsoRequestUtil.secretKey); + return md5(sb.toString()); + } + + /** + * 向参数 Map 中注入 timestamp、nonce、sign 三个签名参数 + * @param params 请求参数(已填好业务参数,此方法自动追加签名参数) + */ + public static void addSignParams(Map params) { + params.put("timestamp", String.valueOf(System.currentTimeMillis())); + params.put("nonce", getRandomString(20)); + params.put("sign", computeSign(params)); + } + + /** + * 校验请求中的 sign 参数是否合法 + * @param params 包含 sign 的请求参数 + * @return 签名是否合法 + */ + public static boolean verifySign(Map params) { + String sign = params.get("sign"); + if (sign == null) return false; + return sign.equals(computeSign(params)); + } + + + // -------------------------- 基础工具 + + /** + * MD5 加密 + * @param str 指定字符串 + * @return 加密后的字符串 + */ + public static String md5(String str) { + str = (str == null ? "" : str); + char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + try { + byte[] btInput = str.getBytes(); + MessageDigest mdInst = MessageDigest.getInstance("MD5"); + mdInst.update(btInput); + byte[] md = mdInst.digest(); + int j = md.length; + char[] strA = new char[j * 2]; + int k = 0; + for (byte byte0 : md) { + strA[k++] = hexDigits[byte0 >>> 4 & 0xf]; + strA[k++] = hexDigits[byte0 & 0xf]; + } + return new String(strA); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 生成指定长度的随机字符串 + * @param length 字符串的长度 + * @return 一个随机字符串 + */ + public static String getRandomString(int length) { + String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + Random random = new Random(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(str.charAt(random.nextInt(62))); + } + return sb.toString(); + } + +} diff --git a/sa-token-doc/sso/sso-nosdk.md b/sa-token-doc/sso/sso-nosdk.md index 196c0a85..89d703c8 100644 --- a/sa-token-doc/sso/sso-nosdk.md +++ b/sa-token-doc/sso/sso-nosdk.md @@ -12,14 +12,13 @@ NoSdk 模式(不使用SDK):通过 http 工具类调用接口的方式来 参考 demo:[sa-token-demo-sso3-client-nosdk](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk) -该 demo 假设应用端没有使用任何“权限认证框架”,使用最基础的 ServletAPI 进行会话管理,模拟了 `/sso/login`、 `/sso/logout`、 `/sso/logoutCall` 三个接口的处理逻辑。 +该 demo 假设应用端没有使用任何“权限认证框架”,使用最基础的 ServletAPI 进行会话管理,模拟了 `/sso/login`、 `/sso/logout`、 `/sso/pushC` 三个接口的处理逻辑。 + +> [!INFO| label:NoSdk 模式优缺点] +> - 1、支持客户端使用任意技术栈。 +> - 2、代码简单易懂,流程直观清晰。 +> - 3、用 http 工具类模拟 Sa-Token SSO 内部实现,样版代码较多,略显冗余。 -> [!WARNING| label:NoSdk 示例不再主维护] -> 基于以下原因: -> - 1、NoSdk demo 相当于通过 http 工具类再次重写了一遍 Sa-Token SSO 模块代码,繁琐且冗余。 -> - 2、重写的代码无法拥有 Sa-Token SSO 模块全部能力,仅能完成基本对接,算是一个简化版 SDK。 -> -> 自 v1.43.0 版本起,不再主维护 NoSdk 模式,仓库示例仅做留档参考,大家可以转为 ReSdk 模式。 ### ReSdk 模式 @@ -28,11 +27,10 @@ ReSdk 模式(重写SDK部分方法):通过重写框架关键步骤点, 参考 demo:[sa-token-demo-sso3-client-resdk](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-resdk) -> [!INFO| label:ReSdk 模式优点] -> - 1、依然支持客户端使用任意技术栈。 +> [!INFO| label:ReSdk 模式优缺点] +> - 1、支持客户端使用任意技术栈。 > - 2、仅重写少量部分关键代码,即可完成对接。几乎可以得到 Sa-Token SSO 模块全量能力。 - -建议新项目首选 ReSdk 模式作为参考。 +> - 3、此模式需要对 Sa-Token SSO 内部实现较为熟悉,才可以驾驭。