import express from 'express';
import cors from 'cors';
import axios from 'axios';
import nodeRSA from 'node-rsa';
import cookieParser from 'cookie-parser';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
// node-rsa 是 CommonJS 模块,需要使用默认导入
const NodeRSA = nodeRSA;
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const PORT = process.env.PORT || 8889;
// 中间件
app.use(cors({
origin: true,
credentials: true
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// 请求日志中间件(用于调试)
app.use((req, res, next) => {
console.log(`[请求] ${req.method} ${req.path} - ${new Date().toISOString()}`);
next();
});
// 加载自动登录配置
let autoLoginConfig = {};
try {
const configPath = join(__dirname, 'auto-login-config.json');
console.log('正在加载自动登录配置文件:', configPath);
const configData = readFileSync(configPath, 'utf-8');
autoLoginConfig = JSON.parse(configData);
console.log('✓ 已加载自动登录配置');
console.log(' 配置的网站数量:', Object.keys(autoLoginConfig).length);
console.log(' 网站列表:', Object.keys(autoLoginConfig).join(', '));
Object.keys(autoLoginConfig).forEach(siteId => {
const site = autoLoginConfig[siteId];
console.log(` - ${siteId}: ${site.name} (${site.loginMethod})`);
});
} catch (error) {
console.error('✗ 加载自动登录配置失败:', error.message);
console.error(' 错误堆栈:', error.stack);
console.log('将使用默认配置');
}
// RSA 加密函数
function encryptWithRSA(text, publicKey) {
const key = new NodeRSA(publicKey);
return key.encrypt(text, 'base64');
}
// 解析 Cookie
function parseCookies(setCookieHeaders) {
return setCookieHeaders.map(cookie => {
const match = cookie.match(/^([^=]+)=([^;]+)/);
if (match) {
const name = match[1];
const value = match[2];
// 提取其他属性
const pathMatch = cookie.match(/Path=([^;]+)/);
const expiresMatch = cookie.match(/Expires=([^;]+)/);
const maxAgeMatch = cookie.match(/Max-Age=([^;]+)/);
const httpOnlyMatch = cookie.match(/HttpOnly/);
const secureMatch = cookie.match(/Secure/);
const sameSiteMatch = cookie.match(/SameSite=([^;]+)/);
return {
name,
value,
path: pathMatch ? pathMatch[1] : '/',
expires: expiresMatch ? expiresMatch[1] : null,
maxAge: maxAgeMatch ? maxAgeMatch[1] : null,
httpOnly: !!httpOnlyMatch,
secure: !!secureMatch,
sameSite: sameSiteMatch ? sameSiteMatch[1] : null
};
}
return null;
}).filter(Boolean);
}
// 生成跳转 HTML
function generateRedirectHTML(cookieData, targetHost, targetDomain, requestId = '') {
return `
自动登录中...
`;
}
// 处理 RSA 加密表单登录
async function handleRSAEncryptedFormLogin(config, credentials) {
const { targetBaseUrl, loginUrl, loginMethodConfig } = config;
const { publicKey, usernameField, passwordField, captchaField, captchaRequired, contentType, successCode, successField } = loginMethodConfig;
console.log('=== RSA 加密表单登录 ===');
console.log(`目标URL: ${targetBaseUrl}${loginUrl}`);
console.log(`用户名: ${credentials.username}`);
console.log(`密码: ${'*'.repeat(credentials.password.length)}`);
console.log(`内容类型: ${contentType}`);
console.log(`成功标识字段: ${successField || 'code'}, 成功值: ${successCode}`);
// 加密用户名和密码
const usernameEncrypted = encryptWithRSA(credentials.username, publicKey);
const passwordEncrypted = encryptWithRSA(credentials.password, publicKey);
console.log('用户名和密码已加密');
console.log(`加密后用户名长度: ${usernameEncrypted.length}`);
console.log(`加密后密码长度: ${passwordEncrypted.length}`);
// 构建请求数据
const requestData = {
[usernameField]: usernameEncrypted,
[passwordField]: passwordEncrypted
};
if (captchaField) {
requestData[captchaField] = captchaRequired ? '' : '';
}
// 发送登录请求
const headers = {};
let requestBody;
if (contentType === 'application/x-www-form-urlencoded') {
headers['Content-Type'] = 'application/x-www-form-urlencoded';
requestBody = new URLSearchParams(requestData).toString();
} else if (contentType === 'application/json') {
headers['Content-Type'] = 'application/json';
requestBody = JSON.stringify(requestData);
} else {
requestBody = requestData;
}
console.log(`发送登录请求到: ${targetBaseUrl}${loginUrl}`);
console.log(`请求头:`, JSON.stringify(headers, null, 2));
console.log(`请求体长度: ${requestBody.length} 字符`);
const loginResponse = await axios.post(
`${targetBaseUrl}${loginUrl}`,
requestBody,
{
headers,
withCredentials: true,
maxRedirects: 0,
validateStatus: function (status) {
return status >= 200 && status < 400;
}
}
);
console.log(`登录响应状态码: ${loginResponse.status}`);
console.log(`响应头:`, JSON.stringify(loginResponse.headers, null, 2));
console.log(`响应数据:`, JSON.stringify(loginResponse.data, null, 2));
// 检查登录是否成功
const responseData = loginResponse.data || {};
const successValue = successField ? responseData[successField] : responseData.code;
console.log(`成功标识值: ${successValue}, 期望值: ${successCode}`);
if (successValue === successCode) {
const cookies = loginResponse.headers['set-cookie'] || [];
console.log(`登录成功!获取到 ${cookies.length} 个 Cookie`);
cookies.forEach((cookie, index) => {
console.log(`Cookie ${index + 1}: ${cookie.substring(0, 100)}...`);
});
return {
success: true,
cookies: cookies,
response: loginResponse.data
};
} else {
console.error(`登录失败!响应:`, responseData);
return {
success: false,
message: responseData.msg || responseData.message || '登录失败',
response: responseData
};
}
}
// 处理普通表单登录(未加密)
async function handlePlainFormLogin(config, credentials) {
const { targetBaseUrl, loginUrl, loginMethodConfig } = config;
const { usernameField, passwordField, captchaField, contentType, successCode, successField } = loginMethodConfig;
console.log('=== 普通表单登录 ===');
console.log(`目标URL: ${targetBaseUrl}${loginUrl}`);
console.log(`用户名: ${credentials.username}`);
console.log(`密码: ${'*'.repeat(credentials.password.length)}`);
console.log(`内容类型: ${contentType}`);
console.log(`成功标识字段: ${successField || 'code'}, 成功值: ${successCode}`);
// 构建请求数据
const requestData = {
[usernameField]: credentials.username,
[passwordField]: credentials.password
};
if (captchaField) {
requestData[captchaField] = '';
}
// 发送登录请求
const headers = {};
let requestBody;
if (contentType === 'application/x-www-form-urlencoded') {
headers['Content-Type'] = 'application/x-www-form-urlencoded';
requestBody = new URLSearchParams(requestData).toString();
} else if (contentType === 'application/json') {
headers['Content-Type'] = 'application/json';
requestBody = JSON.stringify(requestData);
} else {
requestBody = requestData;
}
console.log(`发送登录请求到: ${targetBaseUrl}${loginUrl}`);
console.log(`请求头:`, JSON.stringify(headers, null, 2));
console.log(`请求体:`, contentType === 'application/json' ? requestBody : requestBody.substring(0, 200) + '...');
const loginResponse = await axios.post(
`${targetBaseUrl}${loginUrl}`,
requestBody,
{
headers,
withCredentials: true,
maxRedirects: 0,
validateStatus: function (status) {
return status >= 200 && status < 400;
}
}
);
console.log(`登录响应状态码: ${loginResponse.status}`);
console.log(`响应数据:`, JSON.stringify(loginResponse.data, null, 2));
// 检查登录是否成功
const responseData = loginResponse.data || {};
const successValue = successField ? responseData[successField] : responseData.code;
console.log(`成功标识值: ${successValue}, 期望值: ${successCode}`);
if (successValue === successCode) {
const cookies = loginResponse.headers['set-cookie'] || [];
console.log(`登录成功!获取到 ${cookies.length} 个 Cookie`);
cookies.forEach((cookie, index) => {
console.log(`Cookie ${index + 1}: ${cookie.substring(0, 100)}...`);
});
return {
success: true,
cookies: cookies,
response: loginResponse.data
};
} else {
console.error(`登录失败!响应:`, responseData);
return {
success: false,
message: responseData.msg || responseData.message || '登录失败',
response: responseData
};
}
}
// 通用的自动登录端点
app.get('/api/auto-login/:siteId', async (req, res) => {
const startTime = Date.now();
const requestId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
// 立即输出日志,确认请求已到达
console.log('\n' + '='.repeat(80));
console.log(`[${requestId}] ⚡⚡⚡ 收到自动登录请求!⚡⚡⚡`);
console.log(`[${requestId}] 时间: ${new Date().toISOString()}`);
console.log(`[${requestId}] 请求路径: ${req.path}`);
console.log(`[${requestId}] 请求方法: ${req.method}`);
console.log(`[${requestId}] 完整URL: ${req.protocol}://${req.get('host')}${req.originalUrl}`);
console.log(`[${requestId}] 客户端IP: ${req.ip || req.connection.remoteAddress || req.socket.remoteAddress}`);
console.log(`[${requestId}] User-Agent: ${req.get('user-agent') || 'Unknown'}`);
try {
const { siteId } = req.params;
console.log(`[${requestId}] 网站ID: ${siteId}`);
// 获取网站配置
const config = autoLoginConfig[siteId];
if (!config) {
console.error(`[${requestId}] 错误: 未找到网站ID "${siteId}" 的配置`);
console.error(`[${requestId}] 可用的网站ID: ${Object.keys(autoLoginConfig).join(', ') || '无'}`);
return res.status(404).json({
success: false,
message: `未找到网站ID "${siteId}" 的配置`,
availableSites: Object.keys(autoLoginConfig)
});
}
console.log(`[${requestId}] 网站名称: ${config.name}`);
console.log(`[${requestId}] 目标地址: ${config.targetBaseUrl}`);
console.log(`[${requestId}] 登录方法: ${config.loginMethod}`);
// 获取登录凭据(优先使用环境变量)
const envUsername = process.env[config.credentials.envUsername];
const envPassword = process.env[config.credentials.envPassword];
const credentials = {
username: envUsername || config.credentials.username,
password: envPassword || config.credentials.password
};
console.log(`[${requestId}] 凭据来源: ${envUsername ? '环境变量' : '配置文件'}`);
console.log(`[${requestId}] 用户名: ${credentials.username}`);
console.log(`[${requestId}] 密码: ${'*'.repeat(credentials.password.length)}`);
if (!credentials.username || !credentials.password) {
console.error(`[${requestId}] 错误: 登录凭据未配置`);
return res.status(400).json({
success: false,
message: '登录凭据未配置'
});
}
// 根据登录方法处理登录
let loginResult;
console.log(`[${requestId}] 开始执行登录...`);
switch (config.loginMethod) {
case 'rsa-encrypted-form':
loginResult = await handleRSAEncryptedFormLogin(config, credentials);
break;
case 'plain-form':
loginResult = await handlePlainFormLogin(config, credentials);
break;
default:
console.error(`[${requestId}] 错误: 不支持的登录方法: ${config.loginMethod}`);
return res.status(400).json({
success: false,
message: `不支持的登录方法: ${config.loginMethod}`
});
}
if (!loginResult.success) {
console.error(`[${requestId}] 登录失败:`, loginResult.message);
console.error(`[${requestId}] 失败响应:`, JSON.stringify(loginResult.response, null, 2));
const duration = Date.now() - startTime;
console.log(`[${requestId}] 总耗时: ${duration}ms`);
console.log('='.repeat(80) + '\n');
// 返回错误页面而不是 JSON
const errorHtml = `
自动登录失败
❌
自动登录失败
${loginResult.message}
请求ID: ${requestId}
网站: ${config.name}
详细信息:
${JSON.stringify(loginResult.response, null, 2)}
`;
return res.status(500).send(errorHtml);
}
console.log(`[${requestId}] 登录成功!`);
// 解析 Cookie
const cookieData = parseCookies(loginResult.cookies);
console.log(`[${requestId}] 解析到 ${cookieData.length} 个 Cookie:`);
cookieData.forEach((cookie, index) => {
console.log(`[${requestId}] Cookie ${index + 1}: ${cookie.name} = ${cookie.value.substring(0, 20)}...`);
});
// 生成跳转 HTML(添加更多调试信息)
console.log(`[${requestId}] 生成跳转页面,目标: http://${config.targetHost}/`);
const html = generateRedirectHTML(
cookieData,
config.targetHost,
config.targetDomain,
requestId
);
// 在响应头中设置 Cookie
console.log(`[${requestId}] 设置响应头 Cookie...`);
loginResult.cookies.forEach((cookie, index) => {
// 修改 Cookie 的 Domain,移除端口号
let modifiedCookie = cookie.replace(/Domain=[^;]+/i, `Domain=${config.targetDomain}`);
res.setHeader('Set-Cookie', modifiedCookie);
console.log(`[${requestId}] 设置 Cookie ${index + 1}: ${modifiedCookie.substring(0, 80)}...`);
});
const duration = Date.now() - startTime;
console.log(`[${requestId}] 总耗时: ${duration}ms`);
console.log(`[${requestId}] 返回跳转页面`);
console.log('='.repeat(80) + '\n');
res.send(html);
} catch (error) {
const duration = Date.now() - startTime;
console.error(`[${requestId}] 自动登录异常:`, error.message);
console.error(`[${requestId}] 错误堆栈:`, error.stack);
if (error.response) {
console.error(`[${requestId}] 响应状态:`, error.response.status);
console.error(`[${requestId}] 响应头:`, JSON.stringify(error.response.headers, null, 2));
console.error(`[${requestId}] 响应数据:`, JSON.stringify(error.response.data, null, 2));
}
if (error.request) {
console.error(`[${requestId}] 请求信息:`, {
url: error.config?.url,
method: error.config?.method,
headers: error.config?.headers
});
}
console.log(`[${requestId}] 总耗时: ${duration}ms`);
console.log('='.repeat(80) + '\n');
res.status(500).json({
success: false,
message: '自动登录失败: ' + error.message,
error: process.env.NODE_ENV === 'development' ? error.stack : undefined
});
}
});
// 获取所有配置的网站列表
app.get('/api/auto-login', (req, res) => {
const sites = Object.keys(autoLoginConfig).map(siteId => ({
id: siteId,
name: autoLoginConfig[siteId].name,
endpoint: `/api/auto-login/${siteId}`
}));
res.json({ sites });
});
// 健康检查端点
app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
port: PORT,
configuredSites: Object.keys(autoLoginConfig)
});
});
// 测试端点 - 用于验证配置
app.get('/api/test/:siteId', (req, res) => {
const { siteId } = req.params;
const config = autoLoginConfig[siteId];
if (!config) {
return res.json({
success: false,
message: `未找到网站ID "${siteId}" 的配置`,
availableSites: Object.keys(autoLoginConfig)
});
}
const envUsername = process.env[config.credentials.envUsername];
const envPassword = process.env[config.credentials.envPassword];
const credentials = {
username: envUsername || config.credentials.username,
password: envPassword || config.credentials.password
};
res.json({
success: true,
siteId,
config: {
name: config.name,
targetBaseUrl: config.targetBaseUrl,
loginMethod: config.loginMethod,
loginUrl: config.loginUrl,
hasCredentials: !!(credentials.username && credentials.password),
credentialsSource: envUsername ? '环境变量' : '配置文件',
username: credentials.username,
passwordLength: credentials.password ? credentials.password.length : 0
}
});
});
app.listen(PORT, '0.0.0.0', () => {
console.log('\n' + '='.repeat(80));
console.log('🚀 后端服务器启动成功!');
console.log('='.repeat(80));
console.log(`📍 本地地址: http://localhost:${PORT}`);
console.log(`📍 服务器地址: http://0.0.0.0:${PORT}`);
console.log(`📍 外部访问: http://222.243.138.146:${PORT} (通过防火墙端口映射)`);
console.log(`\n📋 已配置的自动登录网站: ${Object.keys(autoLoginConfig).join(', ') || '无'}`);
console.log(`\n🔗 可用端点:`);
console.log(` - 健康检查: http://localhost:${PORT}/api/health`);
console.log(` - 测试配置: http://localhost:${PORT}/api/test/:siteId`);
console.log(` - 自动登录: http://localhost:${PORT}/api/auto-login/:siteId`);
console.log(`\n💡 提示: 确保防火墙已配置端口映射 (前端:8888, 后端:8889 -> 外网)`);
console.log('='.repeat(80) + '\n');
});