feat(sso): 补全最新版 SSO NoSdk 模式实现

This commit is contained in:
click33
2026-04-01 05:25:48 +08:00
parent 47dff7059d
commit c0f781a9fb
6 changed files with 270 additions and 218 deletions

View File

@@ -47,6 +47,18 @@ sa-token:
push-url: http://sa-sso-client1.com:9003/sso/pushC push-url: http://sa-sso-client1.com:9003/sso/pushC
# 接口调用秘钥 (如果不配置则使用全局默认秘钥) # 接口调用秘钥 (如果不配置则使用全局默认秘钥)
secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor 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采用 ReSdk 模式对接
sso-client3-resdk: sso-client3-resdk:
# 应用名称 # 应用名称

View File

@@ -16,9 +16,6 @@ public class SaSsoClientNoSdkApplication {
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9004"); System.out.println("测试访问应用端三: http://sa-sso-client3.com:9004");
System.out.println("测试前需要根据官网文档修改hosts文件测试账号密码sa / 123456"); System.out.println("测试前需要根据官网文档修改hosts文件测试账号密码sa / 123456");
System.out.println(); System.out.println();
System.err.println("自 v1.43.0 版本起Sa-Token SSO 不再维护 NoSdk 示例,此项目仅做留档");
System.err.println("如您需要非 Sa-Token 技术栈项目接入 SSO-Server 认证中心,请参考 ReSdk 版本示例");
} }
} }

View File

@@ -1,22 +1,22 @@
package com.pj.sso; package com.pj.sso;
import java.io.IOException; import com.pj.sso.util.AjaxJson;
import java.util.Objects; import com.pj.sso.util.MyHttpSessionHolder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.pj.sso.util.AjaxJson; import javax.servlet.http.HttpServletRequest;
import com.pj.sso.util.MyHttpSessionHolder; 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 * @author click33
*/ */
@RestController @RestController
@@ -25,162 +25,166 @@ public class SsoClientController {
// SSO-Client端首页 // SSO-Client端首页
@RequestMapping("/") @RequestMapping("/")
public String index(HttpSession session) { public String index(HttpSession session) {
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" + Object userId = session.getAttribute("userId");
"<p>当前会话登录账号:" + session.getAttribute("userId") + "</p>" + String str = "<h2>Sa-Token SSO-Client 应用端 (模式三-NoSdk)</h2>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" + "<p>当前会话是否登录:" + (userId != null) + " (" + userId + ")</p>" +
" <a href='/sso/logout?back=' + + encodeURIComponent(location.href);>注销</a>" + "<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
" <a href='/sso/logout?back=' + + encodeURIComponent(location.href);>注销</a>" +
" <a href='/sso/myInfo' target=\"_blank\">获取资料</a></p>"; " <a href='/sso/myInfo' target=\"_blank\">获取资料</a></p>";
return str; return str;
} }
// SSO-Client端单点登录地址 // SSO-Client端单点登录地址
@RequestMapping("/sso/login") @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 { HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
// 如果已经登录,则直接返回 // 如果已经登录,则直接返回
if(session.getAttribute("userId") != null) { if (session.getAttribute("userId") != null) {
response.sendRedirect(back); response.sendRedirect(back);
return null; return null;
} }
/* /*
* 此时有两种情况: * 此时有两种情况:
* 情况1ticket无值说明此请求是Client端访问需要重定向至SSO认证中心 * 情况1ticket无值说明此请求是Client端访问需要重定向至SSO认证中心
* 情况2ticket有值说明此请求从SSO认证中心重定向而来需要根据ticket进行登录 * 情况2ticket有值说明此请求从SSO认证中心重定向而来需要根据ticket进行登录
*/ */
if(ticket == null) { if (ticket == null) {
String currUrl = request.getRequestURL().toString(); // ------- 情况 1
String clientLoginUrl = currUrl + "?back=" + SsoRequestUtil.encodeUrl(back); // 当前 url形如http://sso-client.com/sso/login?back=xxx
String serverAuthUrl = SsoRequestUtil.authUrl + "?redirect=" + clientLoginUrl; 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); response.sendRedirect(serverAuthUrl);
return null; return null;
} else { } else {
// 获取当前 client 端的单点注销回调地址 // ------- 情况 2
String ssoLogoutCall = ""; // 构建 checkTicket 请求参数,以 ticket 查询 userId
if(SsoRequestUtil.isSlo) { Map<String, String> params = new LinkedHashMap<>();
ssoLogoutCall = request.getRequestURL().toString().replace("/sso/login", "/sso/logoutCall"); params.put("msgType", "checkTicket");
} params.put("client", SsoRequestUtil.clientId);
params.put("ticket", ticket);
// 校验 ticket SsoSignUtil.addSignParams(params);
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳 String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params);
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串 AjaxJson result = SsoRequestUtil.request(pushUrl);
String sign = SsoRequestUtil.getSignByTicket(ticket, ssoLogoutCall, timestamp, nonce); // 参数签名
String checkUrl = SsoRequestUtil.checkTicketUrl + // 200 代表校验成功
"?timestamp=" + timestamp + if (result.getCode() == 200 && !SsoRequestUtil.isEmpty(result.getData())) {
"&nonce=" + nonce + // 登录上
"&sign=" + sign + session.setAttribute("userId", result.getData());
"&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);
// 返回 back 地址 // 返回 back 地址
response.sendRedirect(back); response.sendRedirect(back);
return null; return null;
} else { } else {
// 将 sso-server 回应的消息作为异常抛出 // 将 sso-server 回应的消息作为异常抛出
throw new RuntimeException(result.getMsg()); throw new RuntimeException(result.getMsg());
} }
} }
} }
// SSO-Client端单点注销地址 // SSO-Client端单点注销地址
@RequestMapping("/sso/logout") @RequestMapping("/sso/logout")
public Object ssoLogout(@RequestParam(defaultValue = "/") String back, public Object ssoLogout(@RequestParam(defaultValue = "/") String back,
HttpServletResponse response, HttpSession session) throws IOException { HttpServletResponse response, HttpSession session) throws IOException {
// 如果未登录,则无需注销 // 如果未登录,则无需注销
if(session.getAttribute("userId") == null) { if (session.getAttribute("userId") == null) {
response.sendRedirect(back); response.sendRedirect(back);
return null; return null;
} }
// 调用 sso-server 认证中心单点注销API // 调用 sso-server 认证中心单点注销 API
Object loginId = session.getAttribute("userId"); // 账号id Object loginId = session.getAttribute("userId");
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳 Map<String, String> params = new LinkedHashMap<>();
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串 params.put("msgType", "signout");
String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce); // 参数签名 params.put("client", SsoRequestUtil.clientId);
params.put("loginId", String.valueOf(loginId));
String url = SsoRequestUtil.sloUrl + SsoSignUtil.addSignParams(params);
"?loginId=" + loginId + String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params);
"&timestamp=" + timestamp + AjaxJson result = SsoRequestUtil.request(pushUrl);
"&nonce=" + nonce +
"&sign=" + sign; // 校验响应状态码200 代表成功
AjaxJson result = SsoRequestUtil.request(url); if (result.getCode() == 200) {
// 极端场景下sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
// 校验响应状态码200 代表成功
if(result.getCode() == 200) {
// 极端场景下sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
session.removeAttribute("userId"); session.removeAttribute("userId");
// 返回 back 地址 // 返回 back 地址
response.sendRedirect(back); response.sendRedirect(back);
return null; return null;
} else { } else {
// 将 sso-server 回应的消息作为异常抛出 // 将 sso-server 回应的消息作为异常抛出
throw new RuntimeException(result.getMsg()); throw new RuntimeException(result.getMsg());
} }
} }
// SSO-Client端单点注销回调地址 // SSO-Server 端消息推送接收地址(单点注销回调等)
@RequestMapping("/sso/logoutCall") @RequestMapping("/sso/pushC")
public Object ssoLogoutCall(String loginId, String autoLogout, String timestamp, String nonce, String sign) { public Object ssoPushC(HttpServletRequest request) {
// 校验签名 // 将请求参数收集为 Map<String, String>
String calcSign = SsoRequestUtil.getSignByLogoutCall(loginId, autoLogout, timestamp, nonce); Map<String, String> params = new LinkedHashMap<>();
if(calcSign.equals(sign) == false) { for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
System.out.println("无效签名,拒绝应答:" + sign); params.put(entry.getKey(), entry.getValue()[0]);
return AjaxJson.getError("无效签名,拒绝应答" + sign);
} }
// 注销这个账号id // 校验签名
for (HttpSession session: MyHttpSessionHolder.sessionList) { if (!SsoSignUtil.verifySign(params)) {
Object userId = session.getAttribute("userId"); return AjaxJson.getError("无效签名,拒绝应答");
if(Objects.equals(String.valueOf(userId), loginId)) { }
session.removeAttribute("userId");
// 按 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") @RequestMapping("/sso/myInfo")
public Object myInfo(HttpSession session) { public Object myInfo(HttpSession session) {
// 如果尚未登录 // 如果尚未登录
if(session.getAttribute("userId") == null) { if (session.getAttribute("userId") == null) {
return "尚未登录,无法获取"; return "尚未登录,无法获取";
} }
// 组织 url 参数 Object loginId = session.getAttribute("userId");
Object loginId = session.getAttribute("userId"); // 账号id Map<String, String> params = new LinkedHashMap<>();
String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳 params.put("msgType", "userinfo");
String nonce = SsoRequestUtil.getRandomString(20); // 随机字符串 params.put("client", SsoRequestUtil.clientId);
String sign = SsoRequestUtil.getSign(loginId, timestamp, nonce); // 参数签名 params.put("loginId", String.valueOf(loginId));
SsoSignUtil.addSignParams(params);
String url = SsoRequestUtil.getDataUrl + String pushUrl = SsoRequestUtil.buildUrl(SsoRequestUtil.pushSUrl, params);
"?loginId=" + loginId + AjaxJson result = SsoRequestUtil.request(pushUrl);
"&timestamp=" + timestamp +
"&nonce=" + nonce + // 返回给前端
"&sign=" + sign;
AjaxJson result = SsoRequestUtil.request(url);
// 返回给前端
return result; return result;
} }
// 全局异常拦截 // 全局异常拦截
@ExceptionHandler @ExceptionHandler
public AjaxJson handlerException(Exception e) { public AjaxJson handlerException(Exception e) {
e.printStackTrace(); e.printStackTrace();
return AjaxJson.getError(e.getMessage()); return AjaxJson.getError(e.getMessage());
} }
} }

View File

@@ -5,61 +5,47 @@ import com.pj.sso.util.AjaxJson;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.Map; import java.util.Map;
import java.util.Random;
/** /**
* 封装一些 sso 共用方法 * 封装一些 sso 共用方法
* *
* @author click33 * @author click33
* @since 2022-4-30 * @since 2022-4-30
*/ */
public class SsoRequestUtil { public class SsoRequestUtil {
/** /**
* SSO-Server端主机地址 * SSO-Server 端主机地址
*/ */
public static String serverUrl = "http://sa-sso-server.com:9000"; public static String serverUrl = "http://sa-sso-server.com:9000";
/** /**
* SSO-Server端 统一认证地址 * SSO-Server 端统一认证地址
*/ */
public static String authUrl = serverUrl + "/sso/auth"; 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";
// -------------------------- 工具方法
/** /**
* 打开单点注销功能 * 发出请求,并返回 AjaxJson 结果
*/ * @param url 请求地址(含查询参数)
public static boolean isSlo = true; * @return 返回的结果
/**
* 接口调用秘钥
*/
public static String secretKey = "kQwIOrYvnXmSDkwEiFngrKidMcdrgKor";
// -------------------------- 工具方法
/**
* 发出请求,并返回 SaResult 结果
* @param url 请求地址
* @return 返回的结果
*/ */
public static AjaxJson request(String url) { public static AjaxJson request(String url) {
Map<String, Object> map = Forest.post(url).executeAsMap(); Map<String, Object> map = Forest.post(url).executeAsMap();
@@ -67,75 +53,28 @@ public class SsoRequestUtil {
} }
/** /**
* 根据参数计算签名 * 将参数 Map 拼接到 baseUrl 后面(值进行 URL 编码),返回完整 URL
* @param loginId 账号id * @param baseUrl 基础 URL
* @param timestamp 当前时间戳13位 * @param params 请求参数
* @param nonce 随机字符串 * @return 拼接后的完整 URL
* @return 签名
*/ */
public static String getSign(Object loginId, String timestamp, String nonce) { public static String buildUrl(String baseUrl, Map<String, String> params) {
return md5("loginId=" + loginId + "&nonce=" + nonce + "&timestamp=" + timestamp + "&key=" + secretKey); StringBuilder sb = new StringBuilder(baseUrl).append("?");
} for (Map.Entry<String, String> entry : params.entrySet()) {
// 单点注销回调时构建签名 sb.append(entry.getKey()).append("=").append(encodeUrl(entry.getValue())).append("&");
public static String getSignByLogoutCall(Object loginId, String autoLogout, String timestamp, String nonce) { }
return md5("autoLogout=" + autoLogout + "&loginId=" + loginId + "&nonce=" + nonce + "&timestamp=" + timestamp + "&key=" + secretKey); sb.deleteCharAt(sb.length() - 1);
} return sb.toString();
// 校验ticket 时构建签名
public static String getSignByTicket(String ticket, String ssoLogoutCall, String timestamp, String nonce) {
return md5("nonce=" + nonce + "&ssoLogoutCall=" + ssoLogoutCall + "&ticket=" + ticket + "&timestamp=" + timestamp + "&key=" + secretKey);
} }
/** /**
* 指定元素是否为null或者空字符串 * 指定元素是否为null或者空字符串
* @param str 指定元素 * @param str 指定元素
* @return 是否为null或者空字符串 * @return 是否为null或者空字符串
*/ */
public static boolean isEmpty(Object str) { public static boolean isEmpty(Object str) {
return str == null || "".equals(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编码 * URL编码

View File

@@ -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<String, String> params) {
TreeMap<String, String> sorted = new TreeMap<>(params);
sorted.remove("sign");
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> 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<String, String> 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<String, String> 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();
}
}

View File

@@ -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[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 模式 ### 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) 参考 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 模式优点] > [!INFO| label:ReSdk 模式优点]
> - 1、依然支持客户端使用任意技术栈。 > - 1、支持客户端使用任意技术栈。
> - 2、仅重写少量部分关键代码即可完成对接。几乎可以得到 Sa-Token SSO 模块全量能力。 > - 2、仅重写少量部分关键代码即可完成对接。几乎可以得到 Sa-Token SSO 模块全量能力。
> - 3、此模式需要对 Sa-Token SSO 内部实现较为熟悉,才可以驾驭。
建议新项目首选 ReSdk 模式作为参考。