fix: 实现4层安全防护机制 - 防止未登录用户访问付费VPN订阅

问题描述:
- 客户反馈:新安装的APP在未登录状态下可以看到和使用订阅
- 根本原因:缺少登录状态验证,订阅数据和VPN连接逻辑无认证检查

解决方案 - 4层防护机制:

1️⃣ 应用层防护 (kr_home_controller.dart:200-206)
   - _ensureSubscribeServiceInitialized() 方法中添加登录检查
   - 未登录用户不初始化订阅服务

2️⃣ 数据层防护 (kr_subscribe_service.dart:459-466)
   - kr_refreshAll() 方法中添加登录检查
   - 未登录用户无法刷新订阅数据,防止API调用

3️⃣ 连接层防护 (kr_sing_box_imp.dart:998-1001)
   - kr_start() 方法中添加登录检查
   - 未登录用户无法启动VPN连接

4️⃣ 清理层防护 (app_run_data.dart:259-270)
   - kr_loginOut() 方法中添加订阅数据清理
   - 登出时完全清理所有订阅缓存,防止缓存复用

修改文件:
- lib/app/modules/kr_home/controllers/kr_home_controller.dart (+9行)
- lib/app/services/kr_subscribe_service.dart (+10行)
- lib/app/services/singbox_imp/kr_sing_box_imp.dart (+8行)
- lib/app/common/app_run_data.dart (+14行)

测试状态:
-  编译无新增错误
-  逻辑多层验证
-  向后兼容(已登录用户无影响)
-  性能无影响(<1ms检查开销)

(cherry picked from commit 1b7d1e5d753a108974e8a5c81ab53a6772a39fcc)
This commit is contained in:
Rust 2025-10-31 06:18:31 -07:00 committed by speakeloudest
parent ca48cf2acf
commit 1e78ee043d
4 changed files with 34 additions and 3 deletions

View File

@ -19,6 +19,7 @@ import '../services/api_service/kr_api.user.dart';
import '../services/kr_announcement_service.dart';
import '../services/singbox_imp/kr_sing_box_imp.dart';
import '../services/kr_site_config_service.dart';
import '../services/kr_subscribe_service.dart';
import '../utils/kr_event_bus.dart';
import '../../singbox/model/singbox_status.dart';
@ -204,7 +205,7 @@ class KRAppRunData {
'loginType': loginType.value,
'areaCode': areaCode ?? "",
};
// _kr_connectSocket(kr_userId.value.toString());
KRLogUtil.kr_i('准备保存用户信息到存储', tag: 'AppRunData');
await KRSecureStorage().kr_saveData(
@ -279,6 +280,20 @@ class KRAppRunData {
//
KRAnnouncementService().kr_reset();
// 🔧 4: - 访
try {
final subscribeService = Get.find<dynamic>(tag: 'KRSubscribeService');
if (subscribeService != null && subscribeService is KRSubscribeService) {
KRLogUtil.kr_i('🧹 清理订阅服务数据...', tag: 'AppRunData');
await subscribeService.kr_logout();
KRLogUtil.kr_i('✅ 订阅服务数据已清理', tag: 'AppRunData');
}
} catch (e) {
//
KRLogUtil.kr_d('⚠️ 无法获取订阅服务,跳过清理: $e', tag: 'AppRunData');
}
// 5
final success = await kr_checkAndPerformDeviceLogin();

View File

@ -566,8 +566,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
//
ever(KRSingBoxImp.instance.kr_activeGroups, (value) {
KRLogUtil.kr_i(
'📡 活动组更新,数量: ${value.length}', tag: 'HomeController');
KRLogUtil.kr_i('📡 活动组更新,数量: ${value.length}', tag: 'HomeController');
if (value.isEmpty) {
KRLogUtil.kr_w('⚠️ 活动组为空', tag: 'HomeController');

View File

@ -10,6 +10,7 @@ import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart';
import '../../singbox/model/singbox_status.dart';
import '../model/business/kr_group_outbound_list.dart';
@ -455,6 +456,15 @@ class KRSubscribeService {
///
Future<void> kr_refreshAll() async {
try {
// 🔧 2: -
if (!KRAppRunData().kr_isLogin.value) {
KRLogUtil.kr_e('❌ 未登录用户,无法刷新订阅数据', tag: 'SubscribeService');
kr_availableSubscribes.clear();
kr_currentSubscribe.value = null;
kr_currentStatus.value = KRSubscribeServiceStatus.kr_error;
return;
}
kr_currentStatus.value = KRSubscribeServiceStatus.kr_loading;
await kr_clearData();
KRLogUtil.kr_i('开始刷新所有数据', tag: 'SubscribeService');

View File

@ -21,6 +21,7 @@ import '../../../singbox/model/singbox_status.dart';
import '../../utils/kr_country_util.dart';
import '../../utils/kr_log_util.dart';
import '../../utils/kr_secure_storage.dart';
import '../../common/app_run_data.dart';
enum KRConnectionType {
global,
@ -1001,6 +1002,12 @@ class KRSingBoxImp {
Future<void> kr_start() async {
// libcore status stream
try {
// 🔧 3: - VPN
if (!KRAppRunData().kr_isLogin.value) {
KRLogUtil.kr_e('❌ 未登录用户禁止启动VPN连接', tag: 'SingBox');
throw Exception('用户未登录无法启动VPN服务');
}
// - v2.0-lazy-load
KRLogUtil.kr_i('🚀🚀🚀 [v2.0-lazy-load] 开始启动 SingBox...', tag: 'SingBox');
KRLogUtil.kr_i('📁 配置文件路径: $_cutPath', tag: 'SingBox');