提交
This commit is contained in:
1 parent
a88d87047d
commit
c0de1b9bef
19 files changed
+17083
-202
No files matched your search
+16
-1
@@ -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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user