Files
hzdyjs/mobile/src/utils/encrypt-webcrypto.js
T
zlwu afe3e234e9 feat(mobile): 实现全局路由守卫功能,自动处理用户未登录跳转
✨ 新增功能:
- 全局路由守卫模块 (route-guard.js)
- 多层次权限验证 (登录状态+Token存在+Token过期检查)
- 自动拦截机制 (页面级+跳转级+HTTP级+应用级)
- 401响应自动处理和跳转
- 路由守卫测试页面

🔧 技术实现:
- checkPageAccess() 全局页面权限检查
- guardedNavigate/guardedSwitchTab 带权限检查的跳转
- handleUnauthorized() HTTP拦截器401处理
- 公开页面配置管理
- 优雅的错误提示和跳转逻辑

📱 页面集成:
- App.vue 初始化路由守卫
- profile/workbench 页面添加权限检查
- index 页面优化跳转逻辑
- 新增测试页面验证功能

📚 文档完善:
- 移动端路由守卫使用指南
- 详细的变更记录和技术说明
- 最佳实践和注意事项

🎯 核心效果:
用户登录失效或未登录时,访问任何需要权限的页面都自动跳转到登录页,提升安全性和用户体验。
2025-07-04 22:28:49 +08:00

200 lines
5.8 KiB
JavaScript

// @ts-nocheck
import CryptoJS from 'crypto-js';
// 配置信息
const ENCRYPT_CONFIG = {
// RSA公钥 - 与后端保持一致 (更新为正确版本,包含ijT7h)
RSA_PUBLIC_KEY:
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
CLIENT_ID: 'e5cd7e4891bf95d1d19206ce24a7b32e',
TENANT_ID: '000000',
};
/**
* 生成随机AES密钥
* @param {number} length 密钥长度,默认32位
* @returns {string} AES密钥
*/
export function generateRandomAESKey(length = 32) {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
/**
* AES加密数据
* @param {string} data 待加密的数据
* @param {string} key AES密钥
* @returns {string} 加密后的数据
*/
export function encryptWithAES(data, key) {
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(data, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
}
/**
* Base64字符串转ArrayBuffer
* @param {string} base64
* @returns {ArrayBuffer}
*/
function base64ToArrayBuffer(base64) {
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
/**
* ArrayBuffer转Base64字符串
* @param {ArrayBuffer} buffer
* @returns {string}
*/
function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
/**
* 导入RSA公钥
* @param {string} publicKeyBase64 Base64格式的公钥
* @returns {Promise<CryptoKey>}
*/
async function importRSAPublicKey(publicKeyBase64) {
try {
const keyBuffer = base64ToArrayBuffer(publicKeyBase64);
const key = await crypto.subtle.importKey(
'spki',
keyBuffer,
{
name: 'RSA-OAEP',
hash: 'SHA-1',
},
false,
['encrypt'],
);
return key;
} catch (error) {
console.error('导入RSA公钥失败:', error);
throw error;
}
}
/**
* RSA加密AES密钥 - 使用Web Crypto API
* @param {string} aesKey AES密钥
* @returns {Promise<string|null>} RSA加密后的AES密钥
*/
export async function encryptAESKeyWithRSA(aesKey) {
try {
// 检查Web Crypto API支持
if (!crypto || !crypto.subtle) {
throw new Error('当前环境不支持Web Crypto API');
}
// 导入RSA公钥
const publicKey = await importRSAPublicKey(ENCRYPT_CONFIG.RSA_PUBLIC_KEY);
// 将AES密钥转为Base64(与custom-login-script.js保持一致)
const aesKeyBase64 = btoa(unescape(encodeURIComponent(aesKey)));
// 将字符串转为ArrayBuffer
const dataBuffer = new TextEncoder().encode(aesKeyBase64);
// RSA加密
const encryptedBuffer = await crypto.subtle.encrypt(
{
name: 'RSA-OAEP',
},
publicKey,
dataBuffer,
);
// 转为Base64字符串
const encryptedBase64 = arrayBufferToBase64(encryptedBuffer);
console.log('✅ Web Crypto RSA加密成功,结果长度:', encryptedBase64.length);
return encryptedBase64;
} catch (error) {
console.error('Web Crypto RSA加密失败:', error.message);
return null;
}
}
/**
* 登录数据加密处理
* @param {Object} loginData 登录数据对象
* @returns {Promise<Object>} 包含加密数据和请求头的对象
*/
export async function encryptLoginData(loginData) {
try {
// 1. 生成AES密钥
const aesKey = generateRandomAESKey(32);
console.log('🔑 生成AES密钥:', aesKey);
// 2. 准备完整的登录数据
const fullLoginData = {
tenantId: loginData.tenantId || ENCRYPT_CONFIG.TENANT_ID,
username: loginData.username,
password: loginData.password,
clientId: loginData.clientId || ENCRYPT_CONFIG.CLIENT_ID,
grantType: loginData.grantType || 'password',
};
console.log('📋 登录数据:', JSON.stringify(fullLoginData, null, 2));
// 3. AES加密登录数据
const encryptedData = encryptWithAES(JSON.stringify(fullLoginData), aesKey);
console.log('🔐 AES加密后数据长度:', encryptedData.length);
// 4. RSA加密AES密钥 - 异步操作
const encryptedKey = await encryptAESKeyWithRSA(aesKey);
if (!encryptedKey) {
throw new Error('RSA加密AES密钥失败');
}
console.log('🔑 RSA加密后密钥长度:', encryptedKey.length);
// 5. 返回加密结果和请求头
return {
success: true,
encryptedData: encryptedData,
headers: {
'Content-Type': 'application/json;charset=utf-8',
clientid: ENCRYPT_CONFIG.CLIENT_ID,
'encrypt-key': encryptedKey,
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
isToken: 'false',
isEncrypt: 'true',
repeatSubmit: 'false',
},
};
} catch (error) {
console.error('登录数据加密失败:', error);
return {
success: false,
error: error.message,
};
}
}
/**
* 获取加密配置
*/
export function getEncryptConfig() {
return ENCRYPT_CONFIG;
}