主要修复: - 修复登录流程逻辑顺序,先保存Token再调用getUserInfo API - 解决纯JavaScript RSA加密兼容性问题,替代JSEncrypt - 创建Token测试工具,便于调试和状态检查 - 优化请求头配置,符合后端API要求 技术改进: - 新增encrypt-pure.js:纯JavaScript RSA加密实现 - 新增token-test.js:Token存储和状态测试工具 - 优化request.js:增强Token获取和调试功能 - 更新login/index.vue:修复登录流程和添加测试功能 解决问题: - ✅ authorization请求头不再为空 - ✅ AES+RSA双重加密登录正常工作 - ✅ uni-app环境下RSA加密兼容性问题已解决 - ✅ 完整的登录认证流程验证通过
284 lines
8.3 KiB
JavaScript
284 lines
8.3 KiB
JavaScript
// @ts-nocheck
|
|
import CryptoJS from 'crypto-js';
|
|
import bigInt from 'big-integer';
|
|
|
|
// 配置信息
|
|
const ENCRYPT_CONFIG = {
|
|
// RSA公钥 - 与后端保持一致
|
|
RSA_PUBLIC_KEY:
|
|
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
|
|
CLIENT_ID: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
|
TENANT_ID: '000000',
|
|
};
|
|
|
|
/**
|
|
* Base64解码
|
|
*/
|
|
function base64Decode(str) {
|
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
let result = '';
|
|
let buffer = 0;
|
|
let bits = 0;
|
|
|
|
for (let i = 0; i < str.length; i++) {
|
|
const char = str.charAt(i);
|
|
if (char === '=') break;
|
|
|
|
const index = chars.indexOf(char);
|
|
if (index === -1) continue;
|
|
|
|
buffer = (buffer << 6) | index;
|
|
bits += 6;
|
|
|
|
if (bits >= 8) {
|
|
bits -= 8;
|
|
result += String.fromCharCode((buffer >> bits) & 0xff);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 解析RSA公钥
|
|
*/
|
|
function parseRSAPublicKey(keyStr) {
|
|
try {
|
|
console.log('🔧 开始解析RSA公钥...');
|
|
|
|
// Base64解码
|
|
const binaryStr = base64Decode(keyStr);
|
|
const bytes = [];
|
|
for (let i = 0; i < binaryStr.length; i++) {
|
|
bytes.push(binaryStr.charCodeAt(i));
|
|
}
|
|
|
|
console.log('🔧 公钥字节长度:', bytes.length);
|
|
|
|
// 简化的ASN.1解析 - 跳过前面的头部直接获取n和e
|
|
// 对于我们的固定公钥,直接使用已知的n和e值
|
|
// 这个是从公钥解析得出的正确n和e值
|
|
const n = bigInt(
|
|
'8907294147844816284558538478551088035165434911351789542173700276555680401825462782219485742265212984242385862425017144718658441075282309582555957891823523',
|
|
);
|
|
const e = bigInt('65537'); // 标准的RSA指数
|
|
|
|
console.log('✅ RSA公钥解析成功');
|
|
console.log('🔧 n值长度:', n.toString().length);
|
|
console.log('🔧 e值:', e.toString());
|
|
|
|
return { n, e };
|
|
} catch (error) {
|
|
console.error('❌ RSA公钥解析失败:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PKCS#1 v1.5 填充
|
|
*/
|
|
function pkcs1Padding(data, keyLength) {
|
|
const dataBytes = [];
|
|
for (let i = 0; i < data.length; i++) {
|
|
dataBytes.push(data.charCodeAt(i));
|
|
}
|
|
|
|
const paddingLength = keyLength - 3 - dataBytes.length;
|
|
if (paddingLength < 8) {
|
|
throw new Error('数据太长,无法进行PKCS#1填充');
|
|
}
|
|
|
|
const padded = [0x00, 0x02];
|
|
|
|
// 添加随机填充字节
|
|
for (let i = 0; i < paddingLength; i++) {
|
|
let randomByte;
|
|
do {
|
|
randomByte = Math.floor(Math.random() * 256);
|
|
} while (randomByte === 0);
|
|
padded.push(randomByte);
|
|
}
|
|
|
|
padded.push(0x00);
|
|
padded.push(...dataBytes);
|
|
|
|
return padded;
|
|
}
|
|
|
|
/**
|
|
* 纯JavaScript RSA加密
|
|
*/
|
|
function rsaEncrypt(data, publicKey) {
|
|
try {
|
|
console.log('🔧 开始纯JavaScript RSA加密...');
|
|
console.log('🔧 待加密数据:', data);
|
|
|
|
const keyLength = 64; // 512位密钥 = 64字节
|
|
|
|
// PKCS#1填充
|
|
const paddedBytes = pkcs1Padding(data, keyLength);
|
|
console.log('🔧 填充后字节长度:', paddedBytes.length);
|
|
|
|
// 转换为大整数
|
|
let message = bigInt(0);
|
|
for (let i = 0; i < paddedBytes.length; i++) {
|
|
message = message.multiply(256).add(paddedBytes[i]);
|
|
}
|
|
|
|
console.log('🔧 消息大整数长度:', message.toString().length);
|
|
|
|
// RSA加密: c = m^e mod n
|
|
const encrypted = message.modPow(publicKey.e, publicKey.n);
|
|
console.log('🔧 加密后大整数长度:', encrypted.toString().length);
|
|
|
|
// 转换为字节数组
|
|
const encryptedBytes = [];
|
|
let temp = encrypted;
|
|
for (let i = 0; i < keyLength; i++) {
|
|
encryptedBytes.unshift(temp.mod(256).toJSNumber());
|
|
temp = temp.divide(256);
|
|
}
|
|
|
|
// 转换为Base64
|
|
let binaryStr = '';
|
|
for (let i = 0; i < encryptedBytes.length; i++) {
|
|
binaryStr += String.fromCharCode(encryptedBytes[i]);
|
|
}
|
|
|
|
const base64 = btoa(binaryStr);
|
|
console.log('✅ RSA加密成功,Base64长度:', base64.length);
|
|
|
|
return base64;
|
|
} catch (error) {
|
|
console.error('❌ 纯JavaScript RSA加密失败:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 生成随机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();
|
|
}
|
|
|
|
/**
|
|
* RSA加密AES密钥 - 使用纯JavaScript实现
|
|
* @param {string} aesKey AES密钥
|
|
* @returns {string|null} RSA加密后的AES密钥
|
|
*/
|
|
export function encryptAESKeyWithRSA(aesKey) {
|
|
try {
|
|
console.log('🔧 开始纯JavaScript RSA加密,AES密钥:', aesKey);
|
|
|
|
// 解析RSA公钥
|
|
const publicKey = parseRSAPublicKey(ENCRYPT_CONFIG.RSA_PUBLIC_KEY);
|
|
if (!publicKey) {
|
|
throw new Error('RSA公钥解析失败');
|
|
}
|
|
|
|
// 将AES密钥转为Base64(与custom-login-script.js保持一致)
|
|
const aesKeyBase64 = btoa(unescape(encodeURIComponent(aesKey)));
|
|
console.log('🔧 AES密钥Base64:', aesKeyBase64);
|
|
|
|
// RSA加密
|
|
const encrypted = rsaEncrypt(aesKeyBase64, publicKey);
|
|
|
|
if (!encrypted) {
|
|
throw new Error('纯JavaScript RSA加密失败');
|
|
}
|
|
|
|
console.log('✅ 纯JavaScript RSA加密成功,结果长度:', encrypted.length);
|
|
return encrypted;
|
|
} catch (error) {
|
|
console.error('❌ 纯JavaScript RSA加密失败:', error.message);
|
|
console.error('❌ 错误堆栈:', error.stack);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 登录数据加密处理
|
|
* @param {Object} loginData 登录数据对象
|
|
* @returns {Object} 包含加密数据和请求头的对象
|
|
*/
|
|
export 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 = 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;
|
|
}
|