安全加固:CORS白名单、JWT密钥自动生成、SSRF防护、路径遍历与错误信息泄露修复
Build Backend JAR / build (push) Has been cancelled

This commit is contained in:
2026-08-25 18:05:28 +08:00
parent c8fb93ff29
commit 338493ecaa
12 changed files with 168 additions and 31 deletions
+18 -11
View File
@@ -11,7 +11,6 @@ import zipfile
from datetime import datetime
from pathlib import Path
from urllib.parse import quote
import traceback
import requests
from requests.adapters import HTTPAdapter
@@ -33,6 +32,7 @@ from werkzeug.security import generate_password_hash
from utils.db import get_db
from utils.auth import admin_required, login_required, get_current_admin_role
from utils.ssrf import is_internal_url
from ali_oss import upload_fileobj as oss_upload_fileobj
@@ -47,6 +47,12 @@ _backend_java_session_local = threading.local()
_internal_token_lock = threading.Lock()
IMAGE_VIDEO_DATA_PERMISSION_KEY = 'admin_image_video_task_data'
SHOP_DATA_CRAWL_DATA_PERMISSION_KEY = 'admin_shop_data_crawl_task_data'
def _internal_error(exc, status=500):
"""记录内部错误日志,仅向客户端返回通用消息,避免泄露内部信息。"""
current_app.logger.error('[admin_api] internal error: %s', exc, exc_info=True)
return jsonify({'success': False, 'error': '服务器内部错误,请稍后重试'}), status
try:
VERSION_UPLOAD_MAX_BYTES = int(os.environ.get('VERSION_UPLOAD_MAX_BYTES', str(512 * 1024 * 1024)))
except ValueError:
@@ -1022,7 +1028,7 @@ def list_users():
'admins': admins,
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
return _internal_error(e)
@admin_api.route('/user', methods=['POST'])
@admin_required
@@ -1113,7 +1119,7 @@ def create_user():
except pymysql.IntegrityError:
return jsonify({'success': False, 'error': '用户名已存在'})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
return _internal_error(e)
@admin_api.route('/user/<int:uid>', methods=['PUT'])
@@ -1201,7 +1207,7 @@ def update_user(uid):
pass
return jsonify({'success': False, 'error': str(exc)}), 403
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
return _internal_error(e)
@admin_api.route('/user/<int:uid>', methods=['DELETE'])
@@ -1239,7 +1245,7 @@ def delete_user(uid):
return jsonify({'success': False, 'error': '用户不存在'})
return jsonify({'success': True, 'msg': '删除成功'})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
return _internal_error(e)
# ---------- 生成历史 ----------
@@ -1311,7 +1317,7 @@ def history():
})
return jsonify({'success': True, 'items': items, 'total': total, 'page': page, 'page_size': page_size})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
return _internal_error(e)
# ---------- 栏目权限配置 ----------
@@ -1634,7 +1640,7 @@ def list_shop_data_crawl_tasks():
except ValueError as exc:
return jsonify({'success': False, 'error': str(exc)}), 400
except Exception as exc:
return jsonify({'success': False, 'error': str(exc)}), 500
return _internal_error(exc)
def _load_shop_data_crawl_download_rows(result_ids):
@@ -1914,6 +1920,8 @@ def download_image_video_tasks_zip():
filename = f'task-{task_id}-video-{video_index + 1}.{extension}'
remote_response = None
try:
if is_internal_url(url):
raise ValueError('拒绝下载内网/本机地址的视频')
remote_response = requests.get(url, stream=True, timeout=(10, 120))
remote_response.raise_for_status()
with output_zip.open(filename, mode='w', force_zip64=True) as target:
@@ -2017,7 +2025,7 @@ def list_image_video_tasks():
except ValueError as exc:
return jsonify({'success': False, 'error': str(exc)}), 400
except Exception as exc:
return jsonify({'success': False, 'error': str(exc)}), 500
return _internal_error(exc)
@admin_api.route('/image-video-tasks/<int:task_id>')
@@ -2573,7 +2581,7 @@ def list_versions():
]
return jsonify({'success': True, 'items': items})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
return _internal_error(e)
# ========== 数字人版本管理(代理到 Java 后端)==========
@@ -2778,8 +2786,7 @@ def upload_version():
'msg': '上传成功',
})
except Exception as e:
traceback.print_exc()
return jsonify({'success': False, 'error': str(e)})
return _internal_error(e)
finally:
if conn is not None:
try:
+2 -1
View File
@@ -49,8 +49,9 @@ def login():
return jsonify({'success': True, 'redirect': url_for('main.admin_page')})
return redirect(url_for('main.admin_page'))
except Exception as exc:
current_app.logger.error('[auth] login error: %s', exc, exc_info=True)
if wants_json:
return jsonify({'success': False, 'error': str(exc)})
return jsonify({'success': False, 'error': '登录失败,请稍后重试'})
return render_html('login.html', error='登录失败,请稍后重试')
if wants_json:
return jsonify({'success': False, 'error': '用户名或密码错误'})
+4 -5
View File
@@ -3,6 +3,7 @@
"""
import os
from flask import Blueprint, redirect, url_for, send_file, session
from werkzeug.utils import safe_join
from utils.auth import login_required, is_session_user_valid
from utils.render import render_html
@@ -29,9 +30,7 @@ def admin_page():
@main.route('/static/<path:filename>')
def serve_static(filename):
"""提供 static 目录及子目录下的静态文件访问"""
filepath = os.path.normpath(os.path.join(STATIC_DIR, filename))
static_abs = os.path.abspath(STATIC_DIR)
file_abs = os.path.abspath(filepath)
if not file_abs.startswith(static_abs) or not os.path.isfile(file_abs):
filepath = safe_join(STATIC_DIR, filename)
if filepath is None or not os.path.isfile(filepath):
return '', 404
return send_file(file_abs, as_attachment=False)
return send_file(filepath, as_attachment=False)
+3 -2
View File
@@ -2,7 +2,7 @@
版本公开 API 蓝图:当前版本、最新版本下载链接
"""
import os
from flask import Blueprint, jsonify
from flask import Blueprint, jsonify, current_app
from utils.db import get_db
@@ -37,4 +37,5 @@ def api_version_latest():
'file_url': row['file_url'] or '',
})
except Exception as e:
return jsonify({'error': str(e)}), 500
current_app.logger.error('[version] internal error: %s', e, exc_info=True)
return jsonify({'error': '服务器内部错误,请稍后重试'}), 500