ZHIWEI666 commited on
Commit
6309acf
·
verified ·
1 Parent(s): 7d1c681

Upload 20 files

Browse files
Files changed (3) hide show
  1. requirements.txt +3 -1
  2. verify_code_engine.py +39 -5
  3. 安全认证.py +31 -23
requirements.txt CHANGED
@@ -12,4 +12,6 @@ httpx==0.25.2
12
  python-alipay-sdk==3.1.0
13
  aiofiles==23.2.1
14
  # 🚀 P2优化:速率限制
15
- slowapi==0.1.9
 
 
 
12
  python-alipay-sdk==3.1.0
13
  aiofiles==23.2.1
14
  # 🚀 P2优化:速率限制
15
+ slowapi==0.1.9
16
+ # 🔒 P0安全增强:bcrypt密码哈希
17
+ bcrypt==4.1.2
verify_code_engine.py CHANGED
@@ -15,6 +15,8 @@ import urllib.request
15
  import urllib.parse
16
  import base64
17
 
 
 
18
  # ==========================================
19
  # 验证码内存缓存 (全局共享字典)
20
  # ==========================================
@@ -23,14 +25,37 @@ import base64
23
  # 注意:服务重启后缓存会清空,生产环境建议改用 Redis
24
  VERIFY_CODES = {}
25
 
 
 
 
 
26
  # 🚀 P1性能优化:记录上次清理时间,避免频繁清理
27
  _last_cleanup_time = 0
28
  _CLEANUP_INTERVAL = 300 # 5分钟清理一次
29
 
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def cleanup_expired_codes():
32
  """
33
  🚀 P1性能优化:清理过期验证码,防止内存泄漏
 
34
  """
35
  global _last_cleanup_time
36
  now = time.time()
@@ -41,7 +66,7 @@ def cleanup_expired_codes():
41
 
42
  _last_cleanup_time = now
43
 
44
- # 收集过期的键
45
  expired_keys = [
46
  key for key, data in VERIFY_CODES.items()
47
  if now > data.get("expires_at", data.get("expires", 0))
@@ -51,8 +76,17 @@ def cleanup_expired_codes():
51
  for key in expired_keys:
52
  del VERIFY_CODES[key]
53
 
54
- if expired_keys:
55
- print(f"🧹 已清理 {len(expired_keys)} 个过期验证码,当前缓存: {len(VERIFY_CODES)} 条")
 
 
 
 
 
 
 
 
 
56
 
57
 
58
  def send_email_code(to_email: str, code: str, action: str):
@@ -87,7 +121,7 @@ def send_email_code(to_email: str, code: str, action: str):
87
  <p>您好,</p>
88
  <p>您正在请求<strong>{action_str}</strong>,您的验证码是:</p>
89
  <div style="font-size:24px; font-weight:bold; color:#2196F3; background:#e3f2fd; padding:15px; text-align:center; border-radius:6px; letter-spacing: 5px;">{code}</div>
90
- <p style="color:#888; font-size:12px; margin-top:20px;">该验证码在 10 分钟内有效。如非本人操作,请忽略此邮件。</p>
91
  </div>
92
  </div>
93
  """
@@ -139,7 +173,7 @@ def send_sms_code(phone: str, code: str, action: str):
139
  from_phone = os.environ.get("TWILIO_FROM")
140
 
141
  # 构建短信内容
142
- body = f"【ComfyUI社区】您正在请求{action_str},验证码是:{code},10分钟内有效。"
143
  url = f"https://api.twilio.com/2010-04-01/Accounts/{twilio_sid}/Messages.json"
144
 
145
  # Twilio 使用 Basic Auth 认证
 
15
  import urllib.parse
16
  import base64
17
 
18
+ from fastapi import HTTPException
19
+
20
  # ==========================================
21
  # 验证码内存缓存 (全局共享字典)
22
  # ==========================================
 
25
  # 注意:服务重启后缓存会清空,生产环境建议改用 Redis
26
  VERIFY_CODES = {}
27
 
28
+ # 🔒 发送频率限制配置
29
+ SEND_COOLDOWN = {} # {contact: last_send_timestamp}
30
+ COOLDOWN_SECONDS = 60 # 同一联系方式60秒内最多发送1条验证码
31
+
32
  # 🚀 P1性能优化:记录上次清理时间,避免频繁清理
33
  _last_cleanup_time = 0
34
  _CLEANUP_INTERVAL = 300 # 5分钟清理一次
35
 
36
 
37
+ def check_send_cooldown(contact: str):
38
+ """
39
+ 🔒 检查发送频率限制
40
+
41
+ 作用:防止同一联系方式频繁发送验证码
42
+ 参数:
43
+ - contact: 联系方式(邮箱或手机号)
44
+ 抛出:
45
+ - HTTPException: 如果在冷却期内,返回429状态码
46
+ """
47
+ now = time.time()
48
+ last_send = SEND_COOLDOWN.get(contact, 0)
49
+ if now - last_send < COOLDOWN_SECONDS:
50
+ remaining = int(COOLDOWN_SECONDS - (now - last_send))
51
+ raise HTTPException(status_code=429, detail=f"请等待{remaining}秒后再发送验证码")
52
+ SEND_COOLDOWN[contact] = now
53
+
54
+
55
  def cleanup_expired_codes():
56
  """
57
  🚀 P1性能优化:清理过期验证码,防止内存泄漏
58
+ 🔒 同时清理过期的发送频率限制记录
59
  """
60
  global _last_cleanup_time
61
  now = time.time()
 
66
 
67
  _last_cleanup_time = now
68
 
69
+ # 收集过期的验证码
70
  expired_keys = [
71
  key for key, data in VERIFY_CODES.items()
72
  if now > data.get("expires_at", data.get("expires", 0))
 
76
  for key in expired_keys:
77
  del VERIFY_CODES[key]
78
 
79
+ # 🔒 清理过期的发送频率限制记录,防止内存泄漏
80
+ expired_cooldown_contacts = [
81
+ contact for contact, last_send in SEND_COOLDOWN.items()
82
+ if now - last_send >= COOLDOWN_SECONDS
83
+ ]
84
+ for contact in expired_cooldown_contacts:
85
+ del SEND_COOLDOWN[contact]
86
+
87
+ total_cleaned = len(expired_keys) + len(expired_cooldown_contacts)
88
+ if total_cleaned > 0:
89
+ print(f"🧹 已清理 {len(expired_keys)} 个过期验证码,{len(expired_cooldown_contacts)} 个过期冷却记录,当前缓存: {len(VERIFY_CODES)} 条验证码,{len(SEND_COOLDOWN)} 条冷却记录")
90
 
91
 
92
  def send_email_code(to_email: str, code: str, action: str):
 
121
  <p>您好,</p>
122
  <p>您正在请求<strong>{action_str}</strong>,您的验证码是:</p>
123
  <div style="font-size:24px; font-weight:bold; color:#2196F3; background:#e3f2fd; padding:15px; text-align:center; border-radius:6px; letter-spacing: 5px;">{code}</div>
124
+ <p style="color:#888; font-size:12px; margin-top:20px;">该验证码在 5 分钟内有效。如非本人操作,请忽略此邮件。</p>
125
  </div>
126
  </div>
127
  """
 
173
  from_phone = os.environ.get("TWILIO_FROM")
174
 
175
  # 构建短信内容
176
+ body = f"【ComfyUI社区】您正在请求{action_str},验证码是:{code},5分钟内有效。"
177
  url = f"https://api.twilio.com/2010-04-01/Accounts/{twilio_sid}/Messages.json"
178
 
179
  # Twilio 使用 Basic Auth 认证
安全认证.py CHANGED
@@ -18,6 +18,9 @@ import time
18
  from typing import Optional, Tuple
19
  from fastapi import HTTPException, Header
20
 
 
 
 
21
 
22
  # ==========================================
23
  # 🔑 安全密钥配置
@@ -40,29 +43,25 @@ TOKEN_EXPIRE_SECONDS = 7 * 24 * 60 * 60
40
 
41
  def hash_password(password: str) -> str:
42
  """
43
- 将明文密码转换为 SHA256 哈希值
44
 
45
  参数:
46
  password: 用户输入的明文密码
47
 
48
  返回:
49
- 64位十六进制哈希字符串
50
 
51
- 示例
52
- hash_password("123456") -> "a8f5f167f44f..."
 
 
53
  """
54
- # 加盐:盐值 + 密码,防止彩虹表攻击
55
- salted_password = f"{PASSWORD_SALT}{password}"
56
-
57
- # SHA256 哈希
58
- hash_obj = hashlib.sha256(salted_password.encode("utf-8"))
59
-
60
- return hash_obj.hexdigest()
61
 
62
 
63
  def verify_password(plain_password: str, hashed_password: str) -> bool:
64
  """
65
- 验证明文密码是否与哈希值匹配
66
 
67
  参数:
68
  plain_password: 用户输入的明文密码
@@ -70,15 +69,26 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:
70
 
71
  返回:
72
  True 匹配 / False 不匹配
 
 
 
 
73
  """
74
- return hash_password(plain_password) == hashed_password
 
 
 
 
 
 
 
75
 
76
 
77
  def require_password_match(stored_password: str, input_password: str) -> bool:
78
  """
79
- 统一密码验证逻辑
80
 
81
- 作用:封装密码验证逻辑,统一处理版未迁移密码的情况
82
 
83
  参数:
84
  stored_password: 数据库中存储的密码(哈希值)
@@ -88,19 +98,17 @@ def require_password_match(stored_password: str, input_password: str) -> bool:
88
  True 密码匹配
89
 
90
  异常:
91
- HTTPException 401: 旧版未迁移密码,需要重置
92
 
93
  使用场景:
94
  - 用户登录时验证密码
95
  - 敏感操作前验证当前密码
96
  """
97
- if len(stored_password) != 64:
98
- # 旧版未迁移的密码,拒绝验证并提示重置
99
- raise HTTPException(
100
- status_code=401,
101
- detail="您的账户需要重置密码以完成安全升级,请使用「忘记密码」功能"
102
- )
103
- return verify_password(input_password, stored_password)
104
 
105
 
106
  # ==========================================
 
18
  from typing import Optional, Tuple
19
  from fastapi import HTTPException, Header
20
 
21
+ # 🔒 P0安全增强:引入bcrypt进行密码哈希
22
+ import bcrypt
23
+
24
 
25
  # ==========================================
26
  # 🔑 安全密钥配置
 
43
 
44
  def hash_password(password: str) -> str:
45
  """
46
+ 使用bcrypt哈希密码(内置唯一盐
47
 
48
  参数:
49
  password: 用户输入的明文密码
50
 
51
  返回:
52
+ bcrypt哈希字符串(以$2b$开头,长度60)
53
 
54
+ 说明
55
+ - 使用bcrypt算法,自动处理盐值
56
+ - rounds=12提供足够的安全性
57
+ - 每个密码哈希都是唯一的
58
  """
59
+ return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=12)).decode('utf-8')
 
 
 
 
 
 
60
 
61
 
62
  def verify_password(plain_password: str, hashed_password: str) -> bool:
63
  """
64
+ 验证密码,兼容旧版SHA256和新版bcrypt
65
 
66
  参数:
67
  plain_password: 用户输入的明文密码
 
69
 
70
  返回:
71
  True 匹配 / False 不匹配
72
+
73
+ 兼容逻辑:
74
+ - bcrypt哈希以'$2b$'或'$2a$'开头,长度60
75
+ - 旧版SHA256哈希为64位十六进制字符串
76
  """
77
+ # 检查是否为bcrypt格式
78
+ if hashed_password.startswith('$2b$') or hashed_password.startswith('$2a$'):
79
+ return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
80
+ else:
81
+ # 兼容旧版SHA256哈希(长度64的十六进制字符串)
82
+ salted = f"{PASSWORD_SALT}{plain_password}"
83
+ old_hash = hashlib.sha256(salted.encode("utf-8")).hexdigest()
84
+ return old_hash == hashed_password
85
 
86
 
87
  def require_password_match(stored_password: str, input_password: str) -> bool:
88
  """
89
+ 统一密码验证,支持bcrypt和旧版SHA256
90
 
91
+ 作用:封装密码验证逻辑,兼容新旧密码格式
92
 
93
  参数:
94
  stored_password: 数据库中存储的密码(哈希值)
 
98
  True 密码匹配
99
 
100
  异常:
101
+ HTTPException 401: 密码为空或密码错误
102
 
103
  使用场景:
104
  - 用户登录时验证密码
105
  - 敏感操作前验证当前密码
106
  """
107
+ if not stored_password:
108
+ raise HTTPException(status_code=401, detail="需要重置密码")
109
+ if not verify_password(input_password, stored_password):
110
+ raise HTTPException(status_code=401, detail="密码错误")
111
+ return True
 
 
112
 
113
 
114
  # ==========================================