feat: 修复 WebSocket 越权与 AI 流 SSRF

This commit is contained in:
weijuesen
2026-08-14 00:45:22 +08:00
parent 1a29def480
commit 839ba91354
17 changed files with 614 additions and 136 deletions
+4 -4
View File
@@ -43,24 +43,24 @@ export function getServerUrl(): string {
// 保留导出以兼容现有代码(模块加载时的默认值,实际请求请使用 getApiBaseUrl()
export const API_BASE_URL = DEFAULT_API_BASE_URL;
export function getWsUrl(token: string): string {
export function getWsUrl(ticket: string): string {
const baseUrl = getApiBaseUrl();
// 如果是完整 URL,从中推导 WS 地址
if (baseUrl.startsWith('http')) {
const serverUrl = baseUrl.replace(/\/api\/v1\/?$/, '');
const wsUrl = serverUrl.replace(/^http/, 'ws');
return `${wsUrl}/ws?token=${token}`;
return `${wsUrl}/ws?ticket=${ticket}`;
}
// H5 相对路径 - 使用 window.location 推导
if (isH5) {
const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = typeof window !== 'undefined' ? window.location.host : 'localhost:3000';
return `${protocol}//${host}/ws?token=${token}`;
return `${protocol}//${host}/ws?ticket=${ticket}`;
}
return `ws://100.83.103.1:3000/ws?token=${token}`;
return `ws://100.83.103.1:3000/ws?ticket=${ticket}`;
}
export const REQUEST_TIMEOUT = 15000;
+19 -5
View File
@@ -1,5 +1,6 @@
import Taro from '@tarojs/taro';
import { getWsUrl } from '@/api/config';
import { get } from '@/api/request';
type MessageHandler = (data: unknown) => void;
@@ -7,19 +8,20 @@ class WebSocketManager {
private connected: boolean = false;
private handlers: Map<string, Set<MessageHandler>> = new Map();
private token: string = '';
private ticket: string = '';
private reconnectAttempts: number = 0;
private maxReconnectAttempts: number = 5;
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
private isManualClose: boolean = false;
connect(token: string): void {
async connect(token: string): Promise<void> {
this.token = token;
this.isManualClose = false;
this.doConnect();
await this.refreshTicketAndConnect();
}
private doConnect(): void {
if (!this.token) {
if (!this.token || !this.ticket) {
console.warn('[WS] 无 token,无法连接 WebSocket');
return;
}
@@ -34,7 +36,7 @@ class WebSocketManager {
this.connected = false;
}
const url = getWsUrl(this.token);
const url = getWsUrl(this.ticket);
console.log('[WS] 正在连接:', url);
// 注册全局回调(每次调用会替换上一次的回调)
@@ -98,10 +100,22 @@ class WebSocketManager {
clearTimeout(this.reconnectTimer);
}
this.reconnectTimer = setTimeout(() => {
this.doConnect();
this.refreshTicketAndConnect();
}, delay);
}
private async refreshTicketAndConnect(): Promise<void> {
if (!this.token) return;
try {
const { ticket } = await get<{ ticket: string }>('/ws/ticket');
this.ticket = ticket;
this.doConnect();
} catch (e) {
console.error('[WS] 获取 ticket 失败:', e);
this.scheduleReconnect();
}
}
on(type: string, handler: MessageHandler): () => void {
if (!this.handlers.has(type)) {
this.handlers.set(type, new Set());