|
@@ -0,0 +1,302 @@
|
|
|
|
+package zhsq_qk.framework.sso;
|
|
|
|
+
|
|
|
|
+import com.dtflys.forest.Forest;
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
+import zhsq_qk.framework.sso.domain.SysLoginUser;
|
|
|
|
+import zhsq_qk.framework.sso.utli.AjaxJson;
|
|
|
|
+import zhsq_qk.framework.sso.utli.JsonUtil;
|
|
|
|
+
|
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
|
+import java.net.URLEncoder;
|
|
|
|
+import java.security.MessageDigest;
|
|
|
|
+import java.util.Map;
|
|
|
|
+import java.util.Random;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * 封装一些 sso 共用方法
|
|
|
|
+ *
|
|
|
|
+ * @author kong
|
|
|
|
+ * @since: 2022-4-30
|
|
|
|
+ */
|
|
|
|
+@Service
|
|
|
|
+public class SsoRequestUtil {
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ public String host = "http://192.168.10.12:3000";
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * SSO-Server端 统一认证地址
|
|
|
|
+ */
|
|
|
|
+ public String authUrl;
|
|
|
|
+
|
|
|
|
+ public String getAuthUrl() {
|
|
|
|
+ return authUrl = host + "/sso/auth";
|
|
|
|
+ }
|
|
|
|
+ /**
|
|
|
|
+ * 使用 Http 请求校验ticket
|
|
|
|
+ */
|
|
|
|
+// public static boolean isHttp = true;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * SSO-Server端 ticket校验地址
|
|
|
|
+ */
|
|
|
|
+ public String checkTicketUrl;
|
|
|
|
+
|
|
|
|
+ public String getCheckTicketUrl() {
|
|
|
|
+ return checkTicketUrl = host + "/sso/checkTicket";
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 打开单点注销功能
|
|
|
|
+ */
|
|
|
|
+ public boolean isSlo = true;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 单点注销地址
|
|
|
|
+ */
|
|
|
|
+ public String sloUrl;
|
|
|
|
+
|
|
|
|
+ public String getSloUrl() {
|
|
|
|
+ return sloUrl = host + "/sso/signout";
|
|
|
|
+ }
|
|
|
|
+ /**
|
|
|
|
+ * 接口调用秘钥
|
|
|
|
+ */
|
|
|
|
+ public String secretkey = "YQfyZtAmDbYHTBaHPSx3GZeX7x2ip7ik";
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * SSO-Server端 注销后跳转地址
|
|
|
|
+ */
|
|
|
|
+ public String logoutRedirectUrl;
|
|
|
|
+
|
|
|
|
+ public String getLogoutRedirectUrl() {
|
|
|
|
+ return logoutRedirectUrl = host + "/home";
|
|
|
|
+ }
|
|
|
|
+ /**
|
|
|
|
+ * SSO-Server端 查询userinfo地址
|
|
|
|
+ */
|
|
|
|
+ public String userinfoUrl;
|
|
|
|
+ public String getUserinfoUrl() {
|
|
|
|
+ return userinfoUrl = host + "/sso/userinfo";
|
|
|
|
+ }
|
|
|
|
+ /**
|
|
|
|
+ * 当前 client 的标识,可为 null
|
|
|
|
+ */
|
|
|
|
+ public String client = "";
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // -------------------------- 封装方法
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 拼接 sso 授权地址
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public String buildServerAuthUrl(String clientLoginUrl) {
|
|
|
|
+ String serverAuthUrl = getAuthUrl();
|
|
|
|
+ if(isNotEmpty(client)) {
|
|
|
|
+ serverAuthUrl = joinParam(serverAuthUrl, "client=" + client);
|
|
|
|
+ }
|
|
|
|
+ serverAuthUrl = joinParam(serverAuthUrl, "redirect=" + clientLoginUrl);
|
|
|
|
+ return serverAuthUrl;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 校验 ticket,返回 userId
|
|
|
|
+ * @param ticket ticket 码
|
|
|
|
+ * @param request 请求对象
|
|
|
|
+ * @param currPath 当前接口的访问path
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public Object checkTicket(String ticket, HttpServletRequest request, String currPath) {
|
|
|
|
+ // 校验 ticket 的地址
|
|
|
|
+ String checkUrl = getCheckTicketUrl() + "?ticket=" + ticket;
|
|
|
|
+
|
|
|
|
+ // 如果锁定 client 的标识
|
|
|
|
+ if(isNotEmpty(client)) {
|
|
|
|
+ checkUrl += "&client=" + client;
|
|
|
|
+ }
|
|
|
|
+ // 如果打开了单点注销
|
|
|
|
+ if(isSlo) {
|
|
|
|
+ String ssoLogoutCall = request.getRequestURL().toString().replace(currPath, "/sso/logoutCall");
|
|
|
|
+ checkUrl += "&ssoLogoutCall=" + ssoLogoutCall;
|
|
|
|
+ }
|
|
|
|
+ // 发起请求
|
|
|
|
+ AjaxJson result = request(checkUrl);
|
|
|
|
+
|
|
|
|
+ // 200 代表校验成功
|
|
|
|
+ if(result.getCode() == 200 && SsoRequestUtil.isEmpty(result.getData()) == false) {
|
|
|
|
+ // 登录上
|
|
|
|
+ Object loginId = result.getData();
|
|
|
|
+ return loginId;
|
|
|
|
+ } else {
|
|
|
|
+ // 将 sso-server 回应的消息作为异常抛出
|
|
|
|
+ throw new RuntimeException(result.getMsg());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 获取指定用户id的详细资料 (调用此接口的前提是 sso-server 端开放了 /sso/userinfo 路由)
|
|
|
|
+ */
|
|
|
|
+ public SysLoginUser getUserInfo(Object loginId) {
|
|
|
|
+
|
|
|
|
+ // 组织 url 参数
|
|
|
|
+ String timestamp = String.valueOf(System.currentTimeMillis()); // 时间戳
|
|
|
|
+ String nonce = getRandomString(20); // 随机字符串
|
|
|
|
+ String sign = getSign(loginId, timestamp, nonce, secretkey); // 参数签名
|
|
|
|
+
|
|
|
|
+ // 请求
|
|
|
|
+ String url = getUserinfoUrl() +
|
|
|
|
+ "?loginId=" + loginId +
|
|
|
|
+ "×tamp=" + timestamp +
|
|
|
|
+ "&nonce=" + nonce +
|
|
|
|
+ "&sign=" + sign;
|
|
|
|
+ AjaxJson result = request(url);
|
|
|
|
+
|
|
|
|
+ // 如果返回值的 code 不是200,代表请求失败
|
|
|
|
+ if(result.getCode() == null || result.getCode() != 200) {
|
|
|
|
+ throw new RuntimeException(result.getMsg());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 解析出 user
|
|
|
|
+ SysLoginUser user = JsonUtil.parseObjectToModel(result.getData(), SysLoginUser.class);
|
|
|
|
+ return user;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // -------------------------- 工具方法
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 发出请求,并返回 SaResult 结果
|
|
|
|
+ * @param url 请求地址
|
|
|
|
+ * @return 返回的结果
|
|
|
|
+ */
|
|
|
|
+ public static AjaxJson request(String url) {
|
|
|
|
+ System.out.println("------ 发起请求:" + url);
|
|
|
|
+ Map<String, Object> map = Forest.post(url).executeAsMap();
|
|
|
|
+ return new AjaxJson(map);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 根据参数计算签名
|
|
|
|
+ * @param loginId 账号id
|
|
|
|
+ * @param timestamp 当前时间戳,13位
|
|
|
|
+ * @param nonce 随机字符串
|
|
|
|
+ * @param secretkey 账号id
|
|
|
|
+ * @return 签名
|
|
|
|
+ */
|
|
|
|
+ public static String getSign(Object loginId, String timestamp, String nonce, String secretkey) {
|
|
|
|
+ return md5("loginId=" + loginId + "&nonce=" + nonce + "×tamp=" + timestamp + "&key=" + secretkey);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 指定元素是否为null或者空字符串
|
|
|
|
+ * @param str 指定元素
|
|
|
|
+ * @return 是否为null或者空字符串
|
|
|
|
+ */
|
|
|
|
+ public static boolean isEmpty(Object str) {
|
|
|
|
+ return str == null || "".equals(str);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 指定元素是否不为null或者空字符串
|
|
|
|
+ * @param str 指定元素
|
|
|
|
+ * @return 是否为null或者空字符串
|
|
|
|
+ */
|
|
|
|
+ public static boolean isNotEmpty(Object str) {
|
|
|
|
+ return !isEmpty(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编码
|
|
|
|
+ * @param url see note
|
|
|
|
+ * @return see note
|
|
|
|
+ */
|
|
|
|
+ public static String encodeUrl(String url) {
|
|
|
|
+ try {
|
|
|
|
+ return URLEncoder.encode(url, "UTF-8");
|
|
|
|
+ } catch (UnsupportedEncodingException e) {
|
|
|
|
+ throw new RuntimeException(e);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 在url上拼接上kv参数并返回
|
|
|
|
+ * @param url url
|
|
|
|
+ * @param parameStr 参数, 例如 id=1001
|
|
|
|
+ * @return 拼接后的url字符串
|
|
|
|
+ */
|
|
|
|
+ public static String joinParam(String url, String parameStr) {
|
|
|
|
+ // 如果参数为空, 直接返回
|
|
|
|
+ if(parameStr == null || parameStr.length() == 0) {
|
|
|
|
+ return url;
|
|
|
|
+ }
|
|
|
|
+ if(url == null) {
|
|
|
|
+ url = "";
|
|
|
|
+ }
|
|
|
|
+ int index = url.lastIndexOf('?');
|
|
|
|
+ // ? 不存在
|
|
|
|
+ if(index == -1) {
|
|
|
|
+ return url + '?' + parameStr;
|
|
|
|
+ }
|
|
|
|
+ // ? 是最后一位
|
|
|
|
+ if(index == url.length() - 1) {
|
|
|
|
+ return url + parameStr;
|
|
|
|
+ }
|
|
|
|
+ // ? 是其中一位
|
|
|
|
+ if(index > -1 && index < url.length() - 1) {
|
|
|
|
+ String separatorChar = "&";
|
|
|
|
+ // 如果最后一位是 不是&, 且 parameStr 第一位不是 &, 就增送一个 &
|
|
|
|
+ if(url.lastIndexOf(separatorChar) != url.length() - 1 && parameStr.indexOf(separatorChar) != 0) {
|
|
|
|
+ return url + separatorChar + parameStr;
|
|
|
|
+ } else {
|
|
|
|
+ return url + parameStr;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // 正常情况下, 代码不可能执行到此
|
|
|
|
+ return url;
|
|
|
|
+ }
|
|
|
|
+}
|