核心功能:
1. 多项目切换:header 新增 ProjectDropdown,切项目时自动刷新权限菜单
2. 业务平台 SSO:header 新增「业务平台」按钮,OAuth2 授权码流程无感跳转
3. SSO 回调:/sso-callback 页面接收业务平台跳回的 code,换 IoT token 登录
共享包改动:
- packages/stores:access store 新增 projectId 字段并加入持久化
- packages/effects/layouts:新增 ProjectDropdown 共享组件
apps/web-antd 改动:
- api/request.ts:
· project-id 请求头仅在非 null 时设置(避免 axios 把 null 序列化为字符串 "null")
· X-Client-Id 改读 VITE_APP_CLIENT_ID,允许多个壳应用各自声明
- api/core/sso.ts:ssoCallback 参数走 body,避免 code 出现在浏览器历史/nginx 日志
- api/system/project:新增项目 simple-list API
- constants/sso.ts:集中 IOT_CLIENT_ID/BIZ_CLIENT_ID 等常量;
generateOauthState 用 crypto.randomUUID 生成 state,替代不安全的 Math.random
- store/auth.ts:抽 completeLogin 公共收尾逻辑,新增 ssoLogin 复用
- views/_core/authentication/sso-callback.vue:SSO 回调页;
dev 模式保留时延日志,失败时通过 query 透给登录页
- router/routes/core.ts:/sso-callback 路由 + beforeEnter 守卫
(缺 code 直接拦回登录页,避免死循环)
- layouts/basic.vue:
· 以 ProjectDropdown 替换 TenantDropdown(列表拉取失败兜底隐藏)
· 切项目时调用 fetchUserInfo,避免菜单/权限陈旧
· 新增「业务平台」跳转按钮;state 写 sessionStorage,
生产缺 VITE_BIZ_BASE_URL 时显式报错而非静默回 localhost
· setInterval 在 onUnmounted 中清理
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
35 lines
1.2 KiB
TypeScript
35 lines
1.2 KiB
TypeScript
/**
|
||
* SSO / OAuth2 相关常量
|
||
*
|
||
* 集中维护 client_id、state 存储 key 等跨文件复用的字符串,避免魔法字符串散落。
|
||
*/
|
||
|
||
/** 本平台(物联运维)在芋道后端登记的 OAuth2 客户端编号 */
|
||
export const IOT_CLIENT_ID = 'iot';
|
||
|
||
/** 业务平台在芋道后端登记的 OAuth2 客户端编号 */
|
||
export const BIZ_CLIENT_ID = 'biz';
|
||
|
||
/** 跳业务平台发起 OAuth2 授权时,state 写入 sessionStorage 的 key */
|
||
export const SSO_STATE_STORAGE_KEY = 'sso:biz:state';
|
||
|
||
/** 本平台自身 SSO 回调页面路径 */
|
||
export const SSO_CALLBACK_PATH = '/sso-callback';
|
||
|
||
/**
|
||
* 生成一个密码学安全的随机 state。
|
||
* 首选 crypto.randomUUID;低版本浏览器回退 getRandomValues。
|
||
*/
|
||
export function generateOauthState(): string {
|
||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||
return crypto.randomUUID();
|
||
}
|
||
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
||
const arr = new Uint8Array(16);
|
||
crypto.getRandomValues(arr);
|
||
return Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join('');
|
||
}
|
||
// 极端降级:不会加密但比 Math.random 多一层时间戳
|
||
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
||
}
|