Committed by
GitHub
Merge pull request #19 from ZhangJinHaHaHa/main
注册漏洞修复,增强用户认证系统安全性
Showing
1 changed file
with
106 additions
and
47 deletions
| 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: |
-
Please register or login to post a comment