戒酒的李白
Committed by GitHub

Merge pull request #19 from ZhangJinHaHaHa/main

注册漏洞修复,增强用户认证系统安全性
1 import time 1 import time
2 import hashlib 2 import hashlib
3 -from flask import Blueprint, redirect, render_template, request, Flask, session  
4 - 3 +from flask import Blueprint, redirect, render_template, request, Flask, session, current_app
  4 +from datetime import datetime, timedelta
  5 +import re
5 from utils.query import query 6 from utils.query import query
6 from utils.errorResponse import errorResponse 7 from utils.errorResponse import errorResponse
7 from utils.logger import app_logger as logging 8 from utils.logger import app_logger as logging
  9 +from functools import wraps
  10 +import secrets
8 11
9 ub = Blueprint('user', 12 ub = Blueprint('user',
10 __name__, 13 __name__,
11 url_prefix='/user', 14 url_prefix='/user',
12 template_folder='templates') 15 template_folder='templates')
13 16
  17 +def login_required(f):
  18 + @wraps(f)
  19 + def decorated_function(*args, **kwargs):
  20 + if 'username' not in session:
  21 + return redirect('/user/login')
  22 + return f(*args, **kwargs)
  23 + return decorated_function
  24 +
14 # 密码加密函数 25 # 密码加密函数
15 -def hash_password(password: str, salt: str = 'XiaoXueQi2024') -> str: 26 +def hash_password(password: str, salt: str = None) -> tuple:
16 """ 27 """
17 使用 SHA256 对密码进行加盐哈希 28 使用 SHA256 对密码进行加盐哈希
18 :param password: 用户输入的密码 29 :param password: 用户输入的密码
19 - :param salt: 加盐值,默认值为 'XiaoXueQi2024'  
20 - :return: 哈希后的密码 30 + :param salt: 可选的盐值
  31 + :return: (哈希后的密码, 盐值)
  32 + """
  33 + if not salt:
  34 + salt = secrets.token_hex(16)
  35 + hash_obj = hashlib.sha256()
  36 + hash_obj.update(salt.encode('utf-8'))
  37 + hash_obj.update(password.encode('utf-8'))
  38 + return hash_obj.hexdigest(), salt
  39 +
  40 +def validate_password(password: str) -> bool:
  41 + """
  42 + 验证密码强度
21 """ 43 """
22 - hash_with_salt = hashlib.sha256(salt.encode('utf-8'))  
23 - hash_with_salt.update(password.encode('utf-8'))  
24 - return hash_with_salt.hexdigest()  
25 - 44 + if len(password) < 8:
  45 + return False
  46 + if not re.search(r"[A-Z]", password):
  47 + return False
  48 + if not re.search(r"[a-z]", password):
  49 + return False
  50 + if not re.search(r"\d", password):
  51 + return False
  52 + if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
  53 + return False
  54 + return True
  55 +
26 @ub.route('/login', methods=['GET', 'POST']) 56 @ub.route('/login', methods=['GET', 'POST'])
27 def login(): 57 def login():
28 """ 58 """
29 处理用户登录请求 59 处理用户登录请求
30 - :return: 登录页面或重定向到主页  
31 """ 60 """
32 if request.method == 'GET': 61 if request.method == 'GET':
33 - return render_template('login_and_register.html') # 显示登录页面 62 + return render_template('login_and_register.html')
34 63
35 try: 64 try:
36 username = request.form.get('username') 65 username = request.form.get('username')
@@ -40,59 +69,89 @@ def login(): @@ -40,59 +69,89 @@ def login():
40 logging.warning("登录失败:用户名或密码为空") 69 logging.warning("登录失败:用户名或密码为空")
41 return render_template('login_and_register.html', msg='用户名和密码不能为空') 70 return render_template('login_and_register.html', msg='用户名和密码不能为空')
42 71
43 - # 查询用户  
44 - sql = "SELECT * FROM user WHERE username = %s AND password = %s"  
45 - result = query(sql, [username, password], "select") 72 + # 查询用户和盐值
  73 + sql = "SELECT password, salt FROM user WHERE username = %s"
  74 + result = query(sql, [username], "select")
46 75
47 if result: 76 if result:
48 - session['username'] = username  
49 - logging.info(f"用户 {username} 登录成功")  
50 - return redirect('/page/home')  
51 - else:  
52 - logging.warning(f"用户 {username} 登录失败:用户名或密码错误")  
53 - return render_template('login_and_register.html', msg='用户名或密码错误') 77 + stored_password = result[0]['password']
  78 + salt = result[0]['salt']
  79 +
  80 + # 验证密码
  81 + hashed_input, _ = hash_password(password, salt)
  82 +
  83 + if hashed_input == stored_password:
  84 + session.clear()
  85 + session['username'] = username
  86 + session['login_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  87 + session['csrf_token'] = secrets.token_hex(32)
  88 + session.permanent = True
  89 + current_app.permanent_session_lifetime = timedelta(hours=2)
  90 +
  91 + logging.info(f"用户 {username} 登录成功")
  92 + return redirect('/page/home')
  93 +
  94 + # 使用相同的响应防止用户枚举
  95 + logging.warning(f"登录失败:用户名或密码错误")
  96 + return render_template('login_and_register.html', msg='用户名或密码错误')
54 97
55 except Exception as e: 98 except Exception as e:
56 logging.error(f"登录过程发生错误: {e}") 99 logging.error(f"登录过程发生错误: {e}")
57 return render_template('login_and_register.html', msg='登录失败,请稍后重试') 100 return render_template('login_and_register.html', msg='登录失败,请稍后重试')
58 101
59 -  
60 @ub.route('/register', methods=['GET', 'POST']) 102 @ub.route('/register', methods=['GET', 'POST'])
61 def register(): 103 def register():
62 if request.method == 'GET': 104 if request.method == 'GET':
63 return render_template('login_and_register.html') 105 return render_template('login_and_register.html')
64 - else: 106 +
  107 + try:
  108 + username = request.form.get('username')
  109 + password = request.form.get('password')
65 110
66 - def filter_fn(user):  
67 - return request.form['username'] in user 111 + if not username or not password:
  112 + return errorResponse('用户名和密码不能为空')
68 113
69 - users = query('select * from user', [], 'select')  
70 - filter_list = list(filter(filter_fn, users))  
71 - if len(filter_list):  
72 - return errorResponse('该用户名已被注册')  
73 - else:  
74 - time_tuple = time.localtime(time.time())  
75 - hash_with_salt = hashlib.sha256('XiaoXueQi2024'.encode('utf-8'))  
76 - hash_with_salt.update(request.form['password'].encode('utf-8'))  
77 - query(  
78 - '''  
79 - insert into user(username,password,createTime) values(%s,%s,%s)  
80 - ''', [  
81 - request.form['username'],  
82 - hash_with_salt.hexdigest(),  
83 - str(time_tuple[0]) + '-' + str(time_tuple[1]) + '-' +  
84 - str(time_tuple[2])  
85 - ]) 114 + # 验证用户名格式
  115 + if not re.match(r'^[a-zA-Z0-9_]{4,20}$', username):
  116 + return errorResponse('用户名只能包含字母、数字和下划线,长度4-20位')
86 117
87 - return redirect('/user/login') 118 + # 验证密码强度
  119 + if not validate_password(password):
  120 + return errorResponse('密码必须包含大小写字母、数字和特殊字符,且长度至少8位')
88 121
  122 + # 使用事务处理竞态条件
  123 + try:
  124 + # 检查用户名是否存在
  125 + check_sql = "SELECT COUNT(*) as count FROM user WHERE username = %s"
  126 + result = query(check_sql, [username], "select")
  127 +
  128 + if result[0]['count'] > 0:
  129 + return errorResponse('该用户名已被注册')
89 130
90 -@ub.route('/logOut')  
91 -def logOut():  
92 - session.clear()  
93 - return redirect('/user/login') 131 + # 生成密码哈希和盐值
  132 + hashed_password, salt = hash_password(password)
  133 +
  134 + # 插入新用户
  135 + insert_sql = '''
  136 + INSERT INTO user(username, password, salt, createTime)
  137 + VALUES(%s, %s, %s, %s)
  138 + '''
  139 + current_time = datetime.now().strftime('%Y-%m-%d')
  140 + query(insert_sql, [username, hashed_password, salt, current_time])
  141 +
  142 + logging.info(f"新用户注册成功: {username}")
  143 + return redirect('/user/login')
  144 +
  145 + except Exception as e:
  146 + logging.error(f"注册过程发生错误: {e}")
  147 + return errorResponse('注册失败,请稍后重试')
  148 +
  149 + except Exception as e:
  150 + logging.error(f"注册过程发生错误: {e}")
  151 + return errorResponse('注册失败,请稍后重试')
94 152
95 -@ub.route('/user/logout') 153 +@ub.route('/logout')
  154 +@login_required
96 def logout(): 155 def logout():
97 """用户登出""" 156 """用户登出"""
98 try: 157 try: