Files
iot-device-management-frontend/apps/web-antd/src/constants/sso.ts
lzh 0aa4a2f68e 新增: 多项目切换 + 业务平台 SSO 单点跳转
核心功能:
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>
2026-04-23 00:08:33 +08:00

35 lines
1.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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)}`;
}