mm_os 3.2.8 → 3.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ const mosca = require('mosca');
7
7
  class MQTT {
8
8
  /**
9
9
  * 构造函数
10
- * @param {Object} config 配置参数
10
+ * @param {Object} config 配置参数
11
11
  */
12
12
  constructor(config) {
13
13
  this.config = Object.assign({
@@ -24,26 +24,24 @@ class MQTT {
24
24
  "message_retention": 86400,
25
25
  // 消息频率限制配置
26
26
  "rate_limit": {
27
- // 时间窗口(毫秒)
28
- "time_window": 60000, // 默认1分钟
27
+ // 时间窗口(秒)
28
+ "time_window": 60, // 默认1分钟
29
29
  // 时间窗口内允许的最大消息数
30
30
  "max_messages": 100, // 人脸识别门禁优化:1分钟内最多20条消息(门禁通常每次识别仅发送1-3条消息)
31
- // 拉黑时长(毫秒)
32
- "block_duration": 3600000 // 人脸识别门禁优化:拉黑30分钟(更合理的临时限制时长)
33
- },
34
- // 重连检测配置
35
- "reconnect_detection": {
36
- // 时间窗口(毫秒)
37
- "time_window": 60000, // 默认1分钟
38
- // 最大允许重连次数
39
- "max_reconnect_attempts": 5,
40
- // 同一客户端ID不同IP的禁止时间(毫秒)
41
- "ip_ban_duration": 3600000, // 默认1小时
42
- // 客户端ID禁止重连时间(毫秒)
43
- "client_id_ban_duration": 7200000, // 默认2小时
44
- // 频繁重连时间阈值(毫秒)
45
- "frequent_reconnect_threshold": 30000 // 默认30秒
31
+ // 拉黑时长(秒)
32
+ "block_duration": 3600 // 人脸识别门禁优化:拉黑30分钟(更合理的临时限制时长)
46
33
  },
34
+ // 安全配置 - 扁平化且共用参数
35
+ // 通用安全配置
36
+ "time_window": 60, // 时间窗口(秒)- 共用参数
37
+ "max_attempts": 5, // 最大尝试次数 - 共用参数
38
+ "block_duration": 3600, // 默认拉黑时长(秒)- 共用参数
39
+ // 重连检测特定配置
40
+ "reconnect_ip_duration": 3600, // 同一客户端ID不同IP的禁止时间(秒)
41
+ "reconnect_client_duration": 7200, // 客户端ID禁止重连时间(秒)
42
+ "reconnect_frequency_threshold": 30, // 频繁重连时间阈值(秒)
43
+ // 密码尝试特定配置
44
+ "password_block_ip": true, // 密码错误时是否同时拉黑IP
47
45
  // 受限主题列表,用于限制某些敏感主题只能由特定客户端订阅
48
46
  restrictedTopics: [],
49
47
  // 允许订阅受限主题的客户端列表
@@ -84,18 +82,24 @@ class MQTT {
84
82
  */
85
83
  this.blockedClients = {};
86
84
 
87
- /**
88
- * 客户端连接历史记录
89
- * 格式: { clientId: { ip: 客户端IP, connectCount: 连接次数, lastConnectTime: 上次连接时间, disconnectTime: 上次断开时间, reconnectAttempts: 重连尝试次数 } }
90
- */
91
- this.clientHistory = {};
92
-
93
85
  /**
94
86
  * 被禁止的IP列表
95
87
  * 格式: { ip: 解除禁止时间戳 }
96
88
  */
97
89
  this.bannedIps = {};
98
90
 
91
+ /**
92
+ * 安全记录(合并客户端连接历史和密码错误记录)
93
+ * 格式: {
94
+ * client: { clientId: { ip: 客户端IP, connectCount: 连接次数, lastConnectTime: 上次连接时间, disconnectTime: 上次断开时间, reconnectAttempts: 重连尝试次数 } },
95
+ * password: { username: { count: 错误次数, firstAttempt: 首次尝试时间, lastAttempt: 上次尝试时间, ip: 尝试IP } }
96
+ * }
97
+ */
98
+ this.securityRecords = {
99
+ client: {},
100
+ password: {}
101
+ };
102
+
99
103
  /**
100
104
  * 黑名单存储类型
101
105
  * 可选值: 'memory', 'redis', 'mongodb', 'mysql'
@@ -185,16 +189,16 @@ MQTT.prototype.init = function (config) {
185
189
  factory: mosca.persistence.Redis,
186
190
  ttl: {
187
191
  // TTL for subscriptions
188
- subscriptions: cg.message_retention * 1000,
192
+ subscriptions: cg.message_retention * 1000, // 恢复为毫秒
189
193
  // TTL for packets
190
- packets: cg.message_retention * 1000,
194
+ packets: cg.message_retention * 1000, // 恢复为毫秒
191
195
  }
192
196
  }, conf.backend);
193
197
  } else if (cg.cache === 'mongodb' || cg.cache === 'mongo') {
194
198
  var url = `mongodb://${mongodb.host}:${mongodb.port}/${mongodb.database}`;
195
199
  if (mongodb.user) {
196
200
  url =
197
- `mongodb://${mongodb.user}:${mongodb.password}@${mongodb.host}:${mongodb.port}/${mongodb.database}`
201
+ `mongodb://${mongodb.user}:${mongodb.password}@${mongodb.host}:${mongodb.port}/${mongodb.database}`;
198
202
  }
199
203
  conf.backend = {
200
204
  // 增加了此项
@@ -202,16 +206,16 @@ MQTT.prototype.init = function (config) {
202
206
  url,
203
207
  pubsubCollection: 'ascoltatori',
204
208
  mongo: {}
205
- }
209
+ };
206
210
 
207
211
  // 数据持久化参数设置
208
212
  conf.persistence = Object.assign({
209
213
  factory: mosca.persistence.Mongo,
210
214
  ttl: {
211
215
  // TTL for subscriptions
212
- subscriptions: cg.message_retention * 1000,
216
+ subscriptions: cg.message_retention * 1000, // 恢复为毫秒
213
217
  // TTL for packets
214
- packets: cg.message_retention * 1000,
218
+ packets: cg.message_retention * 1000, // 恢复为毫秒
215
219
  }
216
220
  }, conf.backend);
217
221
  }
@@ -285,7 +289,9 @@ MQTT.prototype.main = function (state) {
285
289
  * 身份验证
286
290
  */
287
291
  sr.authenticate = (client, username, password, callback) => {
288
- $.log.info("收到身份验证", username, password);
292
+ // 正确获取客户端IP地址
293
+ const clientIp = client.connection && client.connection.stream ? client.connection.stream.remoteAddress || 'unknown' : 'unknown';
294
+ $.log.info("收到身份验证", clientIp, client.id, username);
289
295
  // 因为auth现在是异步函数,需要使用async/await处理
290
296
  (async () => {
291
297
  try {
@@ -310,7 +316,6 @@ MQTT.prototype.main = function (state) {
310
316
  return true;
311
317
  };
312
318
 
313
-
314
319
  /**
315
320
  * 验证订阅,决定客户端可以订阅哪些主题
316
321
  */
@@ -347,9 +352,9 @@ MQTT.prototype.subscribed = function (topic, client) {
347
352
  };
348
353
 
349
354
  /**
350
- * 客户端断开连接时的处理
351
- * @param {Object} client 客户端对象
352
- */
355
+ * 客户端断开连接时的处理
356
+ * @param {Object} client 客户端对象
357
+ */
353
358
  MQTT.prototype.clientDisconnected = function (client) {
354
359
  const clientId = client.id;
355
360
  const now = Date.now();
@@ -357,8 +362,8 @@ MQTT.prototype.clientDisconnected = function (client) {
357
362
  $.log.info(`MQTT客户端 ${clientId} 断开连接`);
358
363
 
359
364
  // 更新客户端断开连接时间
360
- if (this.clientHistory[clientId]) {
361
- this.clientHistory[clientId].disconnectTime = now;
365
+ if (this.securityRecords.client[clientId]) {
366
+ this.securityRecords.client[clientId].disconnectTime = now;
362
367
  }
363
368
  };
364
369
 
@@ -372,12 +377,12 @@ MQTT.prototype.published = function (packet, client) {
372
377
  };
373
378
 
374
379
  /**
375
- * 身份验证 - 主要入口函数
376
- * @param {Object} client 客户端
377
- * @param {String} username 用户名
378
- * @param {String} password 密码
379
- * @param {Function} callback 回调函数,回调返回true,则表示验证通过。
380
- */
380
+ * 身份验证 - 主要入口函数
381
+ * @param {Object} client 客户端
382
+ * @param {String} username 用户名
383
+ * @param {String} password 密码
384
+ * @param {Function} callback 回调函数,回调返回true,则表示验证通过。
385
+ */
381
386
  MQTT.prototype.auth = async function (client, username, password, callback) {
382
387
  try {
383
388
  // 检查客户端状态(拉黑、异常重连等)- 现在是异步操作
@@ -397,6 +402,13 @@ MQTT.prototype.auth = async function (client, username, password, callback) {
397
402
  return;
398
403
  }
399
404
 
405
+ // 检查密码错误次数限制(防暴力破解)
406
+ const passwordLimitResult = await this.checkPasswordAttemptLimit(client, username);
407
+ if (passwordLimitResult === false) {
408
+ callback(null, false);
409
+ return;
410
+ }
411
+
400
412
  // 查询用户密码并验证(异步操作,需要callback)
401
413
  this.checkAccount(client, username, passwordStr, callback);
402
414
  } catch (error) {
@@ -421,7 +433,8 @@ MQTT.prototype.checkClientStatus = async function (client) {
421
433
 
422
434
  // 检测是否有异常重连行为
423
435
  // 注意:detectReconnectAbuse内部也有黑名单相关操作,后续可以考虑重构为使用异步函数
424
- if (this.detectReconnectAbuse(client)) {
436
+ const hasReconnectAbuse = await this.detectReconnectAbuse(client);
437
+ if (hasReconnectAbuse) {
425
438
  $.log.warn(`拒绝客户端 ${client.id} 身份验证:检测到异常重连行为`);
426
439
  return false;
427
440
  }
@@ -461,58 +474,68 @@ MQTT.prototype.validateCredentials = function (client, username, passwordStr) {
461
474
  * 查询用户密码并验证
462
475
  * @param {Object} client 客户端
463
476
  * @param {String} username 用户名
464
- * @param {String} passwordStr 密码字符串
477
+ * @param {String} password 密码字符串
465
478
  * @param {Function} callback 回调函数
466
479
  */
467
- MQTT.prototype.checkAccount = function (client, username, passwordStr, callback) {
480
+ MQTT.prototype.checkAccount = async function (client, username, password, callback) {
481
+ if (!username || !password) {
482
+ return;
483
+ }
468
484
  var sql = $.mysql_admin('sys');
469
485
  try {
470
- var db = sql.db();
471
- db.table = this.config.table_name || 'face_device';
486
+ var dbs = sql.db();
472
487
  // 先尝试通过客户端ID和用户名精确匹配
473
- db.getObj({
474
- clientid: client.id,
475
- username: username
476
- }).then((obj) => {
477
- if (!obj) {
478
- $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:用户不存在,用户名: ${username}`);
479
- callback(null, false);
480
- return;
481
- }
482
- // 验证密码
483
- this.verifyPassword(client, username, passwordStr, obj.password, callback);
484
- }).catch((err) => {
485
- $.log.error(`MQTT客户端 ${client.id} 身份验证数据库错误:`, err);
486
- callback(null, false);
487
- return;
488
+
489
+ // 判断设备连接
490
+ var db1 = dbs.new(this.config.table_name || 'face_device', "");
491
+ var obj = await db1.getObj({
492
+ username,
493
+ clientid: client.id
488
494
  });
489
- } catch (error) {
490
- $.log.error(`MQTT客户端 ${client.id} 身份验证数据库错误:`, error);
491
- callback(null, false);
492
- return;
495
+ if (obj) {
496
+ if (obj.available && obj.state) {
497
+ if (obj.password == password) {
498
+ // 账号密码正确
499
+ return callback(null, true);
500
+ }
501
+ else {
502
+ $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:密码错误,用户名: ${username}`);
503
+ // 记录密码错误尝试
504
+ await this.recordPasswordFailure(client, username);
505
+ }
506
+ }
507
+ else {
508
+ $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:账户不可用,用户名: ${username}`);
509
+ }
510
+ } else {
511
+ // 判断应用连接
512
+ var db2 = dbs.new("sys_app", "app_id");
513
+ var o = await db2.getObj({
514
+ appid: username
515
+ });
516
+ if (o) {
517
+ if (o.available) {
518
+ if (o.appsecret == password) {
519
+ // 账号密码正确
520
+ return callback(null, true);
521
+ }
522
+ else {
523
+ $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:密码错误,用户名: ${username}`);
524
+ // 记录密码错误尝试
525
+ await this.recordPasswordFailure(client, username);
526
+ }
527
+ }
528
+ else {
529
+ $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:账户不可用,用户名: ${username}`);
530
+ }
531
+ }
532
+ }
493
533
  }
494
- };
495
-
496
- /**
497
- * 验证密码
498
- * @param {Object} client 客户端
499
- * @param {String} username 用户名
500
- * @param {String} passwordStr 密码字符串
501
- * @param {String} dbPassword 数据库中的密码
502
- * @param {Function} callback 回调函数
503
- */
504
- MQTT.prototype.verifyPassword = function (client, username, passwordStr, dbPassword, callback) {
505
- // 使用简单的密码比较(当前实现,可根据需要扩展为bcrypt验证)
506
- const isMatch = dbPassword === passwordStr;
507
-
508
- if (isMatch) {
509
- $.log.info(`MQTT客户端 ${client.id} 身份验证成功,用户: ${username}`);
510
- } else {
511
- $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:密码不匹配,用户名: ${username}`);
534
+ catch (err) {
535
+ $.log.error(`MQTT客户端 ${client.id} 身份验证数据库错误:`, err);
512
536
  }
513
-
514
- callback(null, isMatch);
515
- };
537
+ callback(null, false);
538
+ }
516
539
 
517
540
  /**
518
541
  * 验证发布,决定客户端可以发布哪些主题
@@ -623,8 +646,8 @@ MQTT.prototype.checkHighFrequency = async function (client) {
623
646
  lastReset: now
624
647
  };
625
648
  } else {
626
- // 检查是否需要重置计数器
627
- if (now - this.messageCounters[clientId].lastReset > config.time_window) {
649
+ // 检查是否需要重置计数器(注意:time_window现在是秒,需要转换为毫秒进行比较)
650
+ if (now - this.messageCounters[clientId].lastReset > config.time_window * 1000) {
628
651
  this.messageCounters[clientId].count = 1;
629
652
  this.messageCounters[clientId].lastReset = now;
630
653
  } else {
@@ -675,88 +698,88 @@ MQTT.prototype.blockClient = async function (client) {
675
698
  };
676
699
 
677
700
  /**
678
- * 异步检测客户端重连行为
679
- * @param {Object} client 客户端对象
680
- * @returns {Promise<Boolean>} true表示检测到异常重连,false表示正常连接
681
- */
701
+ * 异步检测客户端重连行为
702
+ * @param {Object} client 客户端对象
703
+ * @returns {Promise<Boolean>} true表示检测到异常重连,false表示正常连接
704
+ */
682
705
  MQTT.prototype.detectReconnectAbuse = async function (client) {
683
706
  try {
684
707
  const clientId = client.id;
685
708
  const now = Date.now();
686
- const config = this.config.reconnect_detection;
709
+ const config = this.config;
687
710
 
688
- // 从client对象中获取IP(这里可能需要根据实际情况调整获取IP的方式)
689
- const clientIp = client.connection.stream.remoteAddress || 'unknown';
711
+ // 从client对象中获取IP
712
+ const clientIp = client.connection && client.connection.stream ? client.connection.stream.remoteAddress || 'unknown' : 'unknown';
690
713
 
691
- // 检查IP是否被禁止(使用新的异步函数)
692
- const isIpBanned = await this.isIpBanned(clientIp);
693
- if (isIpBanned) {
694
- $.log.warn(`IP ${clientIp} 处于禁止状态,拒绝连接请求`);
695
- return true;
696
- }
714
+ // 检查IP是否被禁止
715
+ const isIpBanned = await this.isIpBanned(clientIp);
716
+ if (isIpBanned) {
717
+ $.log.warn(`IP ${clientIp} 处于禁止状态,拒绝连接请求`);
718
+ return true;
719
+ }
697
720
 
698
- // 检查是否是首次连接
699
- if (!this.clientHistory[clientId]) {
700
- // 首次连接,初始化连接历史
701
- this.clientHistory[clientId] = {
702
- ip: clientIp,
703
- connectCount: 1,
704
- lastConnectTime: now,
705
- disconnectTime: null,
706
- reconnectAttempts: 0
707
- };
708
- return false;
709
- }
721
+ // 检查是否是首次连接
722
+ if (!this.securityRecords.client[clientId]) {
723
+ // 首次连接,初始化连接历史
724
+ this.securityRecords.client[clientId] = {
725
+ ip: clientIp,
726
+ connectCount: 1,
727
+ lastConnectTime: now,
728
+ disconnectTime: null,
729
+ reconnectAttempts: 0
730
+ };
731
+ return false;
732
+ }
710
733
 
711
- // 获取客户端历史记录
712
- const history = this.clientHistory[clientId];
734
+ // 获取客户端历史记录
735
+ const history = this.securityRecords.client[clientId];
713
736
 
714
- // 检查是否是来自不同IP的连接
715
- if (history.ip !== clientIp) {
716
- $.log.warn(`检测到客户端ID ${clientId} 从不同IP连接: 原IP ${history.ip}, 新IP ${clientIp}`);
737
+ // 检查是否是来自不同IP的连接
738
+ if (history.ip !== clientIp) {
739
+ $.log.warn(`检测到客户端ID ${clientId} 从不同IP连接: 原IP ${history.ip}, 新IP ${clientIp}`);
717
740
 
718
- // 记录新IP的连接行为
719
- const oldIp = history.ip;
720
- history.ip = clientIp;
721
- history.connectCount++;
722
- history.lastConnectTime = now;
741
+ // 记录新IP的连接行为
742
+ const oldIp = history.ip;
743
+ history.ip = clientIp;
744
+ history.connectCount++;
745
+ history.lastConnectTime = now;
723
746
 
724
- // 禁止新IP连接 - 使用新的异步函数
725
- try {
726
- await this.addIpToBanlist(clientIp, config.ip_ban_duration);
727
- } catch (error) {
728
- $.log.error(`禁止IP ${clientIp} 时出错:`, error);
747
+ // 禁止新IP连接
748
+ try {
749
+ await this.addIpToBanlist(clientIp, config.reconnect_ip_duration);
750
+ } catch (error) {
751
+ $.log.error(`禁止IP ${clientIp} 时出错:`, error);
752
+ }
753
+ return true;
729
754
  }
730
- return true;
731
- }
732
755
 
733
- // 检查是否在短时间内频繁重连
734
- const timeSinceLastConnect = now - history.lastConnectTime;
756
+ // 检查是否在短时间内频繁重连
757
+ const timeSinceLastConnect = now - history.lastConnectTime;
735
758
 
736
- // 如果两次连接间隔很短,增加重连尝试计数
737
- if (timeSinceLastConnect < config.frequent_reconnect_threshold) {
738
- history.reconnectAttempts++;
739
- } else {
740
- // 重置重连计数
741
- history.reconnectAttempts = 0;
742
- }
759
+ // 如果两次连接间隔很短,增加重连尝试计数
760
+ if (timeSinceLastConnect < config.reconnect_frequency_threshold * 1000) {
761
+ history.reconnectAttempts++;
762
+ } else {
763
+ // 重置重连计数
764
+ history.reconnectAttempts = 0;
765
+ }
743
766
 
744
- // 更新连接计数和时间
745
- history.connectCount++;
746
- history.lastConnectTime = now;
767
+ // 更新连接计数和时间
768
+ history.connectCount++;
769
+ history.lastConnectTime = now;
747
770
 
748
- // 检查是否超过最大重连尝试次数
749
- if (history.reconnectAttempts > config.max_reconnect_attempts) {
750
- $.log.warn(`客户端 ${clientId} 重连过于频繁,已尝试 ${history.reconnectAttempts} 次重连`);
771
+ // 检查是否超过最大重连尝试次数
772
+ if (history.reconnectAttempts > config.max_attempts) {
773
+ $.log.warn(`客户端 ${clientId} 重连过于频繁,已尝试 ${history.reconnectAttempts} 次重连`);
751
774
 
752
- // 禁止该客户端ID重连 - 使用新的异步函数
753
- try {
754
- await this.addClientToBlacklist(clientId, config.client_id_ban_duration);
755
- } catch (error) {
756
- $.log.error(`禁止频繁重连客户端 ${clientId} 时出错:`, error);
775
+ // 禁止该客户端ID重连
776
+ try {
777
+ await this.addClientToBlacklist(clientId, config.reconnect_client_duration);
778
+ } catch (error) {
779
+ $.log.error(`禁止频繁重连客户端 ${clientId} 时出错:`, error);
780
+ }
781
+ return true;
757
782
  }
758
- return true;
759
- }
760
783
 
761
784
  // 正常连接
762
785
  return false;
@@ -767,6 +790,96 @@ MQTT.prototype.detectReconnectAbuse = async function (client) {
767
790
  }
768
791
  };
769
792
 
793
+ /**
794
+ * 异步检查密码尝试次数限制
795
+ * @param {Object} client 客户端
796
+ * @param {String} username 用户名
797
+ * @returns {Promise<Boolean>} true表示未超过限制,false表示已超过限制
798
+ */
799
+ MQTT.prototype.checkPasswordAttemptLimit = async function (client, username) {
800
+ try {
801
+ const now = Date.now();
802
+ const config = this.config;
803
+
804
+ // 从client对象中获取IP
805
+ const clientIp = client.connection && client.connection.stream ? client.connection.stream.remoteAddress || 'unknown' : 'unknown';
806
+
807
+ // 检查用户名是否已被禁止(密码错误次数过多)
808
+ if (this.securityRecords.password[username]) {
809
+ const failureRecord = this.securityRecords.password[username];
810
+ const timeSinceFirstFailure = now - failureRecord.firstAttempt;
811
+ const timeSinceLastFailure = now - failureRecord.lastAttempt;
812
+
813
+ // 如果超过了时间窗口,重置错误计数
814
+ if (timeSinceFirstFailure > config.time_window * 1000) {
815
+ // 重置错误计数
816
+ delete this.securityRecords.password[username];
817
+ return true;
818
+ }
819
+
820
+ // 检查错误次数是否超过限制
821
+ if (failureRecord.count >= config.max_attempts) {
822
+ // 记录被拒绝的尝试
823
+ $.log.warn(`拒绝客户端 ${client.id} (IP: ${clientIp}) 连接:用户名 ${username} 密码错误次数过多`);
824
+ return false;
825
+ }
826
+ }
827
+
828
+ return true;
829
+ } catch (error) {
830
+ $.log.error(`检查客户端 ${client.id} 密码尝试次数限制时出错:`, error);
831
+ // 出错时默认视为未超过限制,避免误判
832
+ return true;
833
+ }
834
+ };
835
+
836
+ /**
837
+ * 异步记录密码错误尝试
838
+ * @param {Object} client 客户端
839
+ * @param {String} username 用户名
840
+ */
841
+ MQTT.prototype.recordPasswordFailure = async function (client, username) {
842
+ try {
843
+ const now = Date.now();
844
+ const config = this.config;
845
+
846
+ // 从client对象中获取IP
847
+ const clientIp = client.connection && client.connection.stream ? client.connection.stream.remoteAddress || 'unknown' : 'unknown';
848
+
849
+ // 初始化或更新密码错误记录
850
+ if (!this.securityRecords.password[username]) {
851
+ this.securityRecords.password[username] = {
852
+ count: 1,
853
+ firstAttempt: now,
854
+ lastAttempt: now,
855
+ ip: clientIp
856
+ };
857
+ } else {
858
+ this.securityRecords.password[username].count++;
859
+ this.securityRecords.password[username].lastAttempt = now;
860
+ }
861
+
862
+ const failureCount = this.securityRecords.password[username].count;
863
+ $.log.warn(`MQTT客户端 ${client.id} (IP: ${clientIp}) 密码错误,用户名: ${username},当前错误次数: ${failureCount}`);
864
+
865
+ // 检查是否达到最大错误次数,需要拉黑
866
+ if (failureCount >= config.max_attempts) {
867
+ // 拉黑客户端
868
+ await this.addClientToBlacklist(client.id, config.block_duration);
869
+
870
+ // 如果配置了同时拉黑IP,也拉黑IP
871
+ if (config.password_block_ip) {
872
+ await this.addIpToBanlist(clientIp, config.block_duration);
873
+ }
874
+
875
+ $.log.warn(`用户名 ${username} 密码错误次数达到上限(${config.max_attempts}次),已拉黑客户端 ${client.id} 和IP ${clientIp},时长 ${config.block_duration / 3600}小时`);
876
+ }
877
+
878
+ } catch (error) {
879
+ $.log.error(`记录客户端 ${client.id} 密码错误时出错:`, error);
880
+ }
881
+ };
882
+
770
883
  /**
771
884
  * 运行
772
885
  * @param {String} state 状态
@@ -825,7 +938,7 @@ MQTT.prototype.isClientBlocked = async function (clientId) {
825
938
  */
826
939
  MQTT.prototype.addClientToBlacklist = async function (clientId, duration) {
827
940
  const now = Date.now();
828
- const unblockTime = now + (duration || this.config.rate_limit.block_duration);
941
+ const unblockTime = now + (duration || this.config.rate_limit.block_duration) * 1000; // 转换为毫秒
829
942
 
830
943
  try {
831
944
  // 根据存储类型选择不同的实现方式
@@ -839,7 +952,7 @@ MQTT.prototype.addClientToBlacklist = async function (clientId, duration) {
839
952
  this.blockedClients[clientId] = unblockTime;
840
953
  }
841
954
 
842
- $.log.warn(`MQTT客户端 ${clientId} 被添加到黑名单,限制时长: ${duration / 3600000}小时`);
955
+ $.log.warn(`MQTT客户端 ${clientId} 被添加到黑名单,限制时长: ${(duration || this.config.rate_limit.block_duration) / 3600}小时`);
843
956
  return true;
844
957
  } catch (error) {
845
958
  $.log.error(`添加客户端 ${clientId} 到黑名单时出错:`, error);
@@ -895,7 +1008,7 @@ MQTT.prototype.isIpBanned = async function (ip) {
895
1008
  */
896
1009
  MQTT.prototype.addIpToBanlist = async function (ip, duration) {
897
1010
  const now = Date.now();
898
- const unbanTime = now + (duration || this.config.reconnect_detection.ip_ban_duration);
1011
+ const unbanTime = now + (duration || this.config.reconnect_ip_duration) * 1000; // 转换为毫秒
899
1012
 
900
1013
  try {
901
1014
  // 根据存储类型选择不同的实现方式
@@ -909,7 +1022,7 @@ MQTT.prototype.addIpToBanlist = async function (ip, duration) {
909
1022
  this.bannedIps[ip] = unbanTime;
910
1023
  }
911
1024
 
912
- $.log.warn(`IP ${ip} 被添加到禁止列表,限制时长: ${duration / 3600000}小时`);
1025
+ $.log.warn(`IP ${ip} 被添加到禁止列表,限制时长: ${(duration || this.config.reconnect_ip_duration) / 3600}小时`);
913
1026
  return true;
914
1027
  } catch (error) {
915
1028
  $.log.error(`添加IP ${ip} 到禁止列表时出错:`, error);
@@ -917,4 +1030,78 @@ MQTT.prototype.addIpToBanlist = async function (ip, duration) {
917
1030
  }
918
1031
  };
919
1032
 
1033
+ /**
1034
+ * 异步从黑名单中移出客户端
1035
+ * @param {String} clientId 客户端ID
1036
+ * @returns {Promise<Boolean>} true表示移出成功,false表示移出失败
1037
+ */
1038
+ MQTT.prototype.removeClientFromBlacklist = async function (clientId) {
1039
+ try {
1040
+ // 根据存储类型选择不同的实现方式
1041
+ if (this.blacklistStorageType === 'memory') {
1042
+ // 内存存储方式
1043
+ if (this.blockedClients[clientId]) {
1044
+ delete this.blockedClients[clientId];
1045
+ $.log.info(`MQTT客户端 ${clientId} 已从黑名单中移出`);
1046
+ return true;
1047
+ } else {
1048
+ $.log.info(`MQTT客户端 ${clientId} 不在黑名单中`);
1049
+ return false;
1050
+ }
1051
+ } else {
1052
+ // 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
1053
+ // 目前仅实现内存存储,其他存储方式需要根据实际需求补充
1054
+ $.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
1055
+ if (this.blockedClients[clientId]) {
1056
+ delete this.blockedClients[clientId];
1057
+ $.log.info(`MQTT客户端 ${clientId} 已从黑名单中移出`);
1058
+ return true;
1059
+ } else {
1060
+ $.log.info(`MQTT客户端 ${clientId} 不在黑名单中`);
1061
+ return false;
1062
+ }
1063
+ }
1064
+ } catch (error) {
1065
+ $.log.error(`从黑名单中移出客户端 ${clientId} 时出错:`, error);
1066
+ return false;
1067
+ }
1068
+ };
1069
+
1070
+ /**
1071
+ * 异步从禁止列表中移出IP
1072
+ * @param {String} ip IP地址
1073
+ * @returns {Promise<Boolean>} true表示移出成功,false表示移出失败
1074
+ */
1075
+ MQTT.prototype.removeIpFromBanlist = async function (ip) {
1076
+ try {
1077
+ // 根据存储类型选择不同的实现方式
1078
+ if (this.blacklistStorageType === 'memory') {
1079
+ // 内存存储方式
1080
+ if (this.bannedIps[ip]) {
1081
+ delete this.bannedIps[ip];
1082
+ $.log.info(`IP ${ip} 已从禁止列表中移出`);
1083
+ return true;
1084
+ } else {
1085
+ $.log.info(`IP ${ip} 不在禁止列表中`);
1086
+ return false;
1087
+ }
1088
+ } else {
1089
+ // 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
1090
+ // 目前仅实现内存存储,其他存储方式需要根据实际需求补充
1091
+ $.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
1092
+ if (this.bannedIps[ip]) {
1093
+ delete this.bannedIps[ip];
1094
+ $.log.info(`IP ${ip} 已从禁止列表中移出`);
1095
+ return true;
1096
+ } else {
1097
+ $.log.info(`IP ${ip} 不在禁止列表中`);
1098
+ return false;
1099
+ }
1100
+ }
1101
+ } catch (error) {
1102
+ $.log.error(`从禁止列表中移出IP ${ip} 时出错:`, error);
1103
+ return false;
1104
+ }
1105
+ };
1106
+
920
1107
  module.exports = MQTT;
package/index.js CHANGED
@@ -41,13 +41,7 @@ class OS {
41
41
  "static": true,
42
42
  "maxAge": 7200,
43
43
  "static_path": "./static",
44
- "proxy": {},
45
- "rateLimit": {
46
- "windowMs": 60 * 1000, // 时间窗口,默认15分钟
47
- "maxRequests": 5000, // 每个时间窗口内的最大请求数
48
- "message": "请求过于频繁,请稍后再试", // 超过限制时的提示信息
49
- "statusCode": 429 // 超过限制时的HTTP状态码
50
- }
44
+ "proxy": {}
51
45
  },
52
46
  "mqtt": {
53
47
  "state": true,
@@ -8,7 +8,7 @@ module.exports = function(server, config) {
8
8
  // 初始化速率限制配置
9
9
  const cg = {
10
10
  // 默认配置
11
- windowMs: 60 * 1000, // 时间窗口,调整为1分钟(更精细的限制)
11
+ windowMs: 60, // 时间窗口,调整为1分钟(更精细的限制)
12
12
  maxRequests: 5000, // 每个时间窗口内的最大请求数,从1000提高到5000
13
13
  message: '请求过于频繁,请稍后再试', // 超过限制时的提示信息
14
14
  statusCode: 429, // 超过限制时的HTTP状态码
@@ -41,7 +41,7 @@ module.exports = function(server, config) {
41
41
  const count = await $.cache.addInt(key, 1);
42
42
 
43
43
  // 设置过期时间
44
- await $.cache.ttl(key, Math.ceil(cg.windowMs / 1000));
44
+ await $.cache.ttl(key, Math.ceil(cg.windowMs));
45
45
 
46
46
  return count || 0;
47
47
  }
@@ -106,7 +106,7 @@ module.exports = function(server, config) {
106
106
  });
107
107
 
108
108
  // 记录中间件初始化信息
109
- $.log.info(`速率限制中间件已加载: ${cg.maxRequests}请求/${cg.windowMs/1000}秒`);
109
+ $.log.info(`速率限制中间件已加载: ${cg.maxRequests}请求/${cg.windowMs}秒`);
110
110
 
111
111
  return server;
112
112
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mm_os",
3
- "version": "3.2.8",
3
+ "version": "3.2.9",
4
4
  "description": "这是超级美眉服务端框架,用于快速构建应用程序。",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test.js CHANGED
@@ -1,5 +1,10 @@
1
1
  var OS = require("./index.js");
2
2
 
3
+ $.sql = $.mysql_admin('sys', __dirname);
4
+ $.sql.config.database = "face";
5
+ $.sql.config.password = "Asd159357";
6
+ $.sql.open();
7
+
3
8
  var os = new OS();
4
9
 
5
10
  os.run();