star-cas-client
Version:
caslogin client for javascript
641 lines (602 loc) • 17.6 kB
JavaScript
/**
* @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;