This commit is contained in:
zlwu committed 2026-06-26 15:30:24 +08:00
1 parent a88d87047d
commit c0de1b9bef
19 files changed
+17083 -202

No files matched your search

+16 -1
View File
@@ -17,6 +17,20 @@ const isWhiteList = (path: string) => {
return whiteList.some((pattern) => isPathMatch(pattern, path));
};
/** 单点登录门户域名,用于未登录重定向到登录页时附带 sso 参数以触发自动统一认证 */
const SSO_REFERRER_HOSTS = ['td.hzdyjsxy.cn', 'hzdyjsxy.cn'];
const isReferrerFromSsoPortal = () => {
const referrer = document.referrer;
if (!referrer) return false;
try {
const host = new URL(referrer).hostname;
return SSO_REFERRER_HOSTS.some((h) => host === h || host.endsWith('.' + h));
} catch {
return false;
}
};
router.beforeEach(async (to, from, next) => {
NProgress.start();
if (getToken()) {
@@ -59,7 +73,8 @@ router.beforeEach(async (to, from, next) => {
next();
} else {
const redirect = encodeURIComponent(to.fullPath || '/');
next(`/login?redirect=${redirect}`); // 否则全部重定向到登录页
const ssoFlag = isReferrerFromSsoPortal() ? '&sso=1' : '';
next(`/login?redirect=${redirect}${ssoFlag}`); // 否则全部重定向到登录页
NProgress.done();
}
}
+115 -8
View File
@@ -5,6 +5,10 @@
<el-progress :percentage="loadingProgress" :show-text="false" />
<span class="loading-text">背景加载中...</span>
</div>
<div class="loading-indicator" v-if="autoCasRedirecting">
<el-progress :percentage="100" :show-text="false" status="success" />
<span class="loading-text">正在跳转统一认证...</span>
</div>
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<div class="title-box">
<h3 class="title">{{ title }}</h3>
@@ -114,6 +118,8 @@ const loginRules: ElFormRules = {
};
const loading = ref(false);
/** 从单点登录门户进入时自动跳转 CAS,展示过渡提示 */
const autoCasRedirecting = ref(false);
// 租户开关
const tenantEnabled = ref(true);
@@ -237,15 +243,104 @@ const doSocialLogin = (type: string) => {
// ======= 统一认证(CAS)相关 =======
// 固定的 CAS 地址与生产前端地址(按需求写死)
const CAS_BASE = 'http://172.16.0.82';
const CAS_BASE = 'http://td.hzdyjsxy.cn:19081';
const CAS_CLIENT_ID = '0i7tcT5tULqvmmbbV8';
const CAS_CLIENT_SECRET = '68c4f7641523452aaf5ef6d0432869f7';
const FRONTEND_REDIRECT_URI_ENCODED = encodeURIComponent('http://172.16.0.67:8882');
const FRONTEND_REDIRECT_URI_ENCODED = encodeURIComponent('http://web.hzdyjsxy.cn:18081');
const CAS_REDIRECT_KEY = 'cas_login_redirect';
/** 单点登录门户域名(Referer 匹配) */
const SSO_REFERRER_HOSTS = ['td.hzdyjsxy.cn', 'hzdyjsxy.cn'];
/** 本系统前端域名(Referer 来自本站时表示会话过期等内部跳转,保留账号密码登录) */
const APP_HOSTS = ['web.hzdyjsxy.cn', 'localhost', '127.0.0.1'];
/** 单点门户固定入口 redirect 目标(与门户配置 login?redirect=/index 一致) */
const SSO_ENTRY_REDIRECT = '/index';
/** 仅生产域名启用 SSO 自动跳转,避免本地 localhost 误触发 CAS 并跳到生产环境 */
const isCasAutoLoginEnabled = (): boolean => {
return window.location.hostname === 'web.hzdyjsxy.cn';
};
const isSsoRedirectEntry = (route: any): boolean => {
const redirectTarget = String(route?.query?.redirect || '').split('?')[0];
return redirectTarget === SSO_ENTRY_REDIRECT;
};
const isTruthyQueryParam = (value: unknown) => {
if (value === undefined || value === null || value === '') return false;
const s = String(value).toLowerCase();
return s === '1' || s === 'true' || s === 'yes';
};
const isReferrerFromOurApp = (): boolean => {
const referrer = document.referrer;
if (!referrer) return false;
try {
const host = new URL(referrer).hostname;
return APP_HOSTS.some((h) => host === h || host.endsWith('.' + h));
} catch {
return false;
}
};
/**
* 点击“统一认证登录”按钮,直接跳转到 CAS 授权页面
* 判断是否从单点登录门户进入(需自动走统一认证,无需用户再点按钮)
* - URL 参数 sso=1 / from=sso|cas(可选,门户未配置时也能识别)
* - Referer 来自学校统一认证/门户域名
* - 典型 SSO 入口:/login?redirect=/index(无额外参数、常不带 Referer),且非本系统内跳转
*/
const isFromSsoPortal = (route: any): boolean => {
if (!isCasAutoLoginEnabled()) return false;
const query = route?.query || {};
if (isTruthyQueryParam(query.sso)) return true;
const from = String(query.from || '').toLowerCase();
if (from === 'sso' || from === 'cas' || from === 'udap') return true;
const referrer = document.referrer;
if (referrer) {
try {
const host = new URL(referrer).hostname;
if (SSO_REFERRER_HOSTS.some((h) => host === h || host.endsWith('.' + h))) return true;
} catch {
/* ignore invalid referrer */
}
}
// 单点门户固定链接 /login?redirect=/index;本地未登录访问 / 时为 redirect=/,不应误判
if (isSsoRedirectEntry(route) && !isReferrerFromOurApp()) {
return true;
}
return false;
};
const getPostLoginRedirect = () => {
const fromStorage = sessionStorage.getItem(CAS_REDIRECT_KEY);
if (fromStorage) {
sessionStorage.removeItem(CAS_REDIRECT_KEY);
return fromStorage;
}
const target = redirect.value || '/';
// CAS 回调经路由守卫带入的 redirect 可能含 code,登录后应去掉
if (target.includes('code=')) {
return target.split('?')[0] || '/';
}
return target;
};
const persistCasRedirectTarget = () => {
const target = redirect.value;
if (target && !target.includes('code=')) {
sessionStorage.setItem(CAS_REDIRECT_KEY, target);
}
};
/**
* 跳转 CAS 授权页(手动点击或 SSO 自动触发)
*/
const handleUnifiedLogin = () => {
persistCasRedirectTarget();
const authorizeUrl = `${CAS_BASE}/cas/oauth2.0/authorize?response_type=code&client_id=${CAS_CLIENT_ID}&redirect_uri=${FRONTEND_REDIRECT_URI_ENCODED}`;
window.location.href = authorizeUrl;
};
@@ -316,12 +411,16 @@ const fetchAccessToken = async (code: string) => {
// 持久化 token,更新 store,并跳转到重定向或首页
setToken(tokenStr);
userStore.token = tokenStr;
const redirectUrl = redirect.value || '/';
await router.push(redirectUrl);
await router.push(getPostLoginRedirect());
return;
}
ElMessage.error((body && body.msg) || '统一认证登录失败,请使用账号密码登录');
autoCasRedirecting.value = false;
} catch (err) {
console.error('fetchAccessToken error', err);
ElMessage.error('统一认证登录失败,请使用账号密码登录');
autoCasRedirecting.value = false;
}
};
@@ -332,11 +431,19 @@ onMounted(() => {
// 开始预加载背景图片
preloadBackgroundImage();
// 如果 URL 中携带 CAS 的 code,则使用该 code 请求 accessToken 并打印返回信息
const code = extractCodeFromRoute(router.currentRoute.value);
const route = router.currentRoute.value;
// 如果 URL 中携带 CAS 的 code,则使用该 code 换取本系统 Token
const code = extractCodeFromRoute(route);
if (code) {
// 发起请求(异步,不阻塞页面加载)
fetchAccessToken(code);
return;
}
// 从单点登录门户进入:自动执行统一认证流程(直接打开登录页不走此分支)
if (isFromSsoPortal(route)) {
autoCasRedirecting.value = true;
handleUnifiedLogin();
}
});
</script>