UNPKG

star-cas-client

Version:

caslogin client for javascript

641 lines (602 loc) 17.6 kB
/** * @name star-cas-client * @version 2.5.7 */ const request = require("superagent"); const SPARK_OK = "SparkOk"; const GET_TGT = "GET_TGT"; const OBTAIN_TGT = "OBTAIN_TGT"; const GET_TICKET = "GET_TICKET"; const ELECTRON_RE = /electron/gi; const IS_ELECTRON_ENV = ELECTRON_RE.test(window.navigator.userAgent); const WHICH_WINDOW = IS_ELECTRON_ENV ? window.top : window; /** * CasClient */ class CasClient { /** * 构造函数 * @param {Object} cfg :配置信息 */ constructor(cfg) { cfg = this.processConfig(cfg); this.casUrl = "/cas/v1/tickets"; this.appCasUrl = "/cas"; /** * 存在登录系统和cas地址不一样的情况 * 1. basePath cas 登录系统地址 或 getinfo.json 文件获取地址 * 2. casApiPath cas的api请求地址 一定是域名 若不存在则使用 basePath 地址 * 注意区分:cas登录系统和cas 的api请求地址 */ this.basePath = cfg.basePath; this.casApiPath = cfg.casApiPath; this.applications = cfg.applications; } setApplications(applications) { this.applications = applications; } /** * process config * @param {Object} cfg 传入的配置信息 * @return {Object} 经过处理的配置信息 */ processConfig(cfg) { if (cfg.basePath === undefined) { cfg.basePath = process.env.VUE_APP_CAS_BASE_PATH; } if (cfg.applications instanceof Array) { cfg.applications.map((app) => { if (app.service === undefined) { app.service = app.basePath + "/cas?client_name=CasClient"; } }); } return cfg; } static setCache(key, value) { WHICH_WINDOW.localStorage.setItem(key, value); } static getCache(key) { return WHICH_WINDOW.localStorage.getItem(key); } static deleteCache(key) { WHICH_WINDOW.localStorage.removeItem(key); } static install(Vue, options) { const _casClient = new CasClient(options); const params = CasClient.parseURL(window.location.href); if (params["tgc"] !== undefined && params["tgc"] !== "") { CasClient.setCache("TGC", params["tgc"]); } Vue.prototype.$casClient = _casClient; } static createIframeLoaded(id, messageCallback) { window.addEventListener("message", messageCallback, false); const iframe = document.createElement("iframe"); iframe.id = id; document.body.appendChild(iframe); return iframe; } static createIframe(id, src, messageCallback) { window.addEventListener("message", messageCallback, false); const iframe = document.createElement("iframe"); iframe.id = id; iframe.src = src; iframe.style.display = "none"; this.app = document.getElementById("app"); this.app.appendChild(iframe); } static removeIframe(id) { const node = document.getElementById(id); if (node != null) { this.app.removeChild(node); } } /** * 获取本地设备号,先从localStorage里取,没有则生成一个新的 * @return {string} 设备号 */ static getGuid() { if (CasClient.getCache("TID") !== null) { return CasClient.getCache("TID"); } function webGuid() { guid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); } let guid = ""; if (IS_ELECTRON_ENV) { const networks = Object.values(WHICH_WINDOW.$node.os.networkInterfaces()); const flatNetworks = []; const ZERO_RE = /[^0:]/; Object.values(networks).forEach((item) => { if (Array.isArray(item)) { flatNetworks.push(...item); } }); guid = flatNetworks.find((item) => { return ZERO_RE.test(item.mac); }); } else { webGuid(); } CasClient.setCache("TID", guid); return guid; } static parseURL(url) { const a = document.createElement("a"); a.href = url; return { source: url, protocol: a.protocol.replace(":", ""), host: a.hostname, port: a.port, query: a.search, fullHost: a.protocol + "//" + a.hostname + (a.port === "" ? "" : ":" + a.port), params: (function () { const ret = {}; const seg = a.search.replace(/^\?/, "").split("&"); const len = seg.length; let i = 0; let s; for (; i < len; i++) { if (!seg[i]) { continue; } s = seg[i].split("="); ret[s[0].toLowerCase()] = s[1]; } return ret; })(), hash: a.hash.replace("#", ""), path: a.pathname.replace(/^([^\/])/, "/$1"), segments: a.pathname.replace(/^\//, "").split("/"), }; } /** * 登录获取TGT * @param {Object} form 登录用户信息 * @return {void} */ login(form) { return new Promise((resolve, reject) => { const requestApi = this.casApiPath || this.basePath; // 请求 api request .post(requestApi + this.casUrl) .send("username=" + form.username) .send("password=" + form.password) .set("tid", CasClient.getGuid()) .then((response) => { const res = response.body || JSON.parse(response.text); if (res.tgc !== undefined) { CasClient.setCache("TGC", res.tgc); } resolve(res); }) .catch((err) => { reject(err); }); }); } /** * 退出CAS服务器 * @return {void} */ logout() { const tgc = CasClient.getCache("TGC"); return new Promise((resolve, reject) => { const requestApi = this.casApiPath || this.basePath; // 请求 api request .delete(requestApi + this.casUrl + "/" + tgc) .set("tgt", tgc) .then((res) => { CasClient.deleteCache("TGC"); resolve(); }) .catch((err) => { reject(err); }); }); } logoutCrossDomain() { const casHost = this.basePath; const ref = location.href; const casSrc = `${casHost}/#/logout?ref=${ref}`; const iframeId = "casIframe"; return new Promise((resolve, reject) => { const onPostMessage = (event) => { const origin = event.origin || event.originalEvent.origin; // 检查origin 安全性 try { if (!casHost.startsWith(origin)) { return false; } const data = JSON.parse(event.data); console.log("logout:", data); if (data.type !== SPARK_OK) return; window.removeEventListener("message", onPostMessage, false); CasClient.removeIframe(iframeId); if (data.data === "success") { // 退出成功 if (process.env.VUE_APP_CUSTOM_LOGIN_PAGE) { // 此处不判断是否是开发环境了 casHost = process.env.VUE_APP_CUSTOM_LOGIN_PAGE; } window.location.href = `${casHost}/#/login`; resolve(true); } else { console.error("退出失败"); reject(new Error("退出失败")); } } catch (error) { reject(error); } }; CasClient.createIframe(iframeId, casSrc, onPostMessage); }); } /** * 检查TGT是否过期 * @return {void} */ checkCasState() { const tgc = CasClient.getCache("TGC"); return new Promise((resolve, reject) => { if (tgc === null) { resolve(false); } else { const requestApi = this.casApiPath || this.basePath; // 请求 api request .get(requestApi + this.casUrl + "/" + tgc) .set("tgt", tgc) .then((res) => { if (/^TGT-.*/.test(res.text)) { resolve(true); } else { resolve(false); } }) .catch((err) => { reject(err); }); } }); } /** * 获取TGT的值 * @return {void} */ obtainCasValue() { const tgc = CasClient.getCache("TGC"); return new Promise((resolve, reject) => { if (tgc === null) { resolve(false); } else { const requestApi = this.casApiPath || this.basePath; // 请求 api request .get(requestApi + this.casUrl + "/" + tgc) .set("tgt", tgc) .then((res) => { if (/^TGT-.*/.test(res.text)) { resolve(res.text); } else { resolve(false); } }) .catch((err) => { reject(err); }); } }); } /** * 跨域获取TGT * @return {void} */ obtainTGTCrossDomain() { const casHost = this.basePath; const casSrc = `${casHost}/#/obtain-tgt-value`; const iframeId = "casIframe"; return new Promise((resolve, reject) => { const onPostMessageCas = (event) => { const origin = event.origin || event.originalEvent.origin; // 检查origin 安全性 try { if (!casHost.startsWith(origin)) { return false; } const data = event.data; if (data.operateType !== OBTAIN_TGT) return; window.removeEventListener("message", onPostMessageCas, false); CasClient.removeIframe(iframeId); resolve(data.data); } catch (error) { reject(error); } }; CasClient.createIframe(iframeId, casSrc, onPostMessageCas); }); } /** * 跨域检查TGT是否过期 * @return {void} */ checkCasStateCrossDomain() { const casHost = this.basePath; const casSrc = `${casHost}/#/get-tgt`; const iframeId = "casIframe"; return new Promise((resolve, reject) => { const onPostMessageCas = (event) => { const origin = event.origin || event.originalEvent.origin; // 检查origin 安全性 try { if (!casHost.startsWith(origin)) { return false; } const data = event.data; if (data.operateType !== GET_TGT) return; window.removeEventListener("message", onPostMessageCas, false); CasClient.removeIframe(iframeId); resolve(data.data); } catch (error) { reject(error); } }; CasClient.createIframe(iframeId, casSrc, onPostMessageCas); }); } /** * 登录业务系统 * @param {String} appName * @return {void} */ loginApp(appName) { const app = this.getApp(appName); if (app == null) throw new Error(appName + " does not exists"); return new Promise((resolve, reject) => { this.getServiceTicket(appName) .then((ticket) => { // 登录业务系统 request .get(app.basePath + this.appCasUrl) .withCredentials() .query("ticket=" + ticket) .then((res) => { resolve(res); }) .catch((err) => { reject(err); }); }) .catch((err) => { reject(err); }); }); } /** * 根据TGT和serviceId换取 ST * @param {String} appName * @return {void} */ getServiceTicket(appName) { const app = this.getApp(appName); if (app == null) throw new Error(appName + " does not exists"); const tgc = CasClient.getCache("TGC"); // 获取ST return new Promise((resolve, reject) => { let casHost = this.basePath; const ref = window.location.href; if (tgc === null) { window.location.href = `${casHost}/#/login?ref=${ref}`; reject(new Error("TGT cannot be null")); } else { const requestApi = this.casApiPath || this.basePath; // 请求 api request .post(requestApi + this.casUrl + "/" + tgc) .set("tgt", tgc) .send("service=" + app.service) .then((res) => { if (/^ST-.*/.test(res.text)) { const tickets = res.text; resolve(tickets); } else { window.location.href = `${casHost}/#/login?ref=${ref}`; reject(new Error("cannot parse st:" + res.text)); } }) .catch((err) => { window.location.href = `${casHost}/#/login?ref=${ref}`; reject(err); }); } }); } /** * 跨域登录应用站点 * @param {应用名称} appName * @return {void} */ loginAppCrossDomain(appName) { let casHost = this.basePath; const ref = location.href; const casSrc = `${casHost}/#/get-ticket?service=${appName}&ref=${ref}`; const app = this.getApp(appName); if (app == null) throw new Error(appName + " does not exists"); const iframeId = "casIframe"; return new Promise((resolve, reject) => { const onPostMessage = (event) => { const origin = event.origin || event.originalEvent.origin; // 检查origin 安全性 try { if (!casHost.startsWith(origin)) { return false; } const data = JSON.parse(event.data); if (data.operateType !== GET_TICKET) return; const ticket = data.data; window.removeEventListener("message", onPostMessage, false); CasClient.removeIframe(iframeId); if (typeof ticket === "string" && ticket.split("-")[0] === "ST") { // 登录业务系统 request .get(app.basePath + this.appCasUrl) .withCredentials() .query("ticket=" + ticket) .then((res) => { resolve("success"); }) .catch((err) => { reject(err); }); } else { if (process.env.VUE_APP_CUSTOM_LOGIN_PAGE) { // 此处不判断是否是开发环境了 casHost = process.env.VUE_APP_CUSTOM_LOGIN_PAGE; } window.location.href = `${casHost}/#/login?ref=${ref}`; } } catch (error) { reject(error); } }; CasClient.createIframe(iframeId, casSrc, onPostMessage); }); } /** * 先检查应用是否登录,没登录的调用跨域登录方法 * @param {应用名称} appName * @return {void} */ checkAndLoginAppCros(appName) { return new Promise((resolve, reject) => { this.checkAppState(appName) .then((result) => { if (result === false) { // 登录 this.loginAppCrossDomain(appName).then((res) => { resolve(1); }); } else { resolve(0); } }) .catch((err) => { reject(err); }); }); } checkAndLoginApp(appName) { return new Promise((resolve, reject) => { this.checkAppState(appName) .then((result) => { if (result === false) { // 登录 this.loginApp(appName) .then((res) => { resolve(1); }) .catch((err) => { reject(err); }); } else { resolve(0); } }) .catch((err) => { reject(err); }); }); } getPreRoute(defaultRoute) { let PRE_PATH = CasClient.getCache("PRE_PATH"); if (PRE_PATH === null) { PRE_PATH = defaultRoute; } CasClient.deleteCache("PRE_PATH"); return PRE_PATH; } getInfo() { return new Promise((resolve, reject) => { request .get(this.basePath + "/getinfo.json") .then((res) => { resolve(res); }) .catch((err) => { reject(err); }); }); } /** * 查看业务系统登录状态 * @param {String} appName * @return {void} */ checkAppState(appName) { const app = this.getApp(appName); if (app == null) throw new Error(appName + " does not exists"); return new Promise((resolve, reject) => { request .get(app.basePath + "/check") .withCredentials() .then((res) => { resolve(res.body); }) .catch((err) => { reject(err); }); }); } getApp(appName) { for (let i = 0; i < this.applications.length; i++) { const app = this.applications[i]; if (app.name === appName) { return app; } } } getCaptchaCode(type) { return new Promise((resolve, reject) => { const tgc = CasClient.getCache("TGC"); if (tgc === null) { reject(new Error("TGT cannot be null")); } else { const requestApi = this.casApiPath || this.basePath; // 请求 api request .get(`${requestApi}/cas/captcha/permission?type=${type}`) .set("tgt", tgc) .then((res) => { resolve(res.body); }) .catch((err) => { reject(err); }); } }); } checkCaptchaCode(params) { return new Promise((resolve, reject) => { const tgc = CasClient.getCache("TGC"); if (tgc === null) { reject(new Error("TGT cannot be null")); } else { const requestApi = this.casApiPath || this.basePath; // 请求 api request .get(`${requestApi}/cas/captcha/check`) .set("tgt", tgc) .set("check", params) .then((res) => { resolve(res.body); }) .catch((err) => { reject(err); }); } }); } } module.exports = CasClient;