131 lines
3.9 KiB
Python
131 lines
3.9 KiB
Python
# ===========================================
|
||
# 班级操行分管理系统 - 后端服务
|
||
#
|
||
# 开发者: Canglan
|
||
# 联系方式: admin@sea-studio.top
|
||
# 版权归属: Sea Network Technology Studio
|
||
# 许可证: MIT License
|
||
#
|
||
# 版权所有 © Sea Network Technology Studio
|
||
# ===========================================
|
||
|
||
import hashlib
|
||
import secrets
|
||
import re
|
||
from config import settings
|
||
|
||
|
||
class SecurityUtils:
|
||
"""安全工具类"""
|
||
|
||
@staticmethod
|
||
def sha1_md5_password(password: str) -> str:
|
||
"""
|
||
双重加密:sha1 + md5
|
||
流程:原始密码 -> sha1 -> 加盐 -> md5
|
||
"""
|
||
# 第一层:SHA1
|
||
sha1_hash = hashlib.sha1(password.encode('utf-8')).hexdigest()
|
||
# 加盐
|
||
salted = sha1_hash + settings.PASSWORD_SALT
|
||
# 第二层:MD5
|
||
md5_hash = hashlib.md5(salted.encode('utf-8')).hexdigest()
|
||
return md5_hash
|
||
|
||
@staticmethod
|
||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||
"""验证密码"""
|
||
return SecurityUtils.sha1_md5_password(plain_password) == hashed_password
|
||
|
||
@staticmethod
|
||
def generate_random_password(length: int = 8) -> str:
|
||
"""生成随机密码"""
|
||
alphabet = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'
|
||
return ''.join(secrets.choice(alphabet) for _ in range(length))
|
||
|
||
@staticmethod
|
||
def validate_password_strength(password: str) -> tuple:
|
||
"""
|
||
验证密码强度
|
||
返回: (是否有效, 错误信息)
|
||
"""
|
||
if len(password) < 6:
|
||
return False, "密码长度至少6位"
|
||
if len(password) > 20:
|
||
return False, "密码长度不能超过20位"
|
||
|
||
# 检查是否包含至少一个数字
|
||
if not any(c.isdigit() for c in password):
|
||
return False, "密码必须包含至少一个数字"
|
||
|
||
# 检查是否包含至少一个字母
|
||
if not any(c.isalpha() for c in password):
|
||
return False, "密码必须包含至少一个字母"
|
||
|
||
return True, ""
|
||
|
||
@staticmethod
|
||
def sanitize_string(value: str, max_length: int = 255) -> str:
|
||
"""
|
||
清理字符串输入
|
||
- 去除首尾空格
|
||
- 限制长度
|
||
- 转义特殊字符
|
||
"""
|
||
if not value:
|
||
return ""
|
||
|
||
# 去除首尾空格
|
||
value = value.strip()
|
||
|
||
# 限制长度
|
||
if len(value) > max_length:
|
||
value = value[:max_length]
|
||
|
||
# 转义HTML特殊字符(防止XSS)
|
||
html_chars = {
|
||
'&': '&',
|
||
'<': '<',
|
||
'>': '>',
|
||
'"': '"',
|
||
"'": ''',
|
||
'/': '/'
|
||
}
|
||
for char, escape in html_chars.items():
|
||
value = value.replace(char, escape)
|
||
|
||
return value
|
||
|
||
@staticmethod
|
||
def validate_student_no(student_no: str) -> bool:
|
||
"""验证学号格式(数字+字母,长度4-20)"""
|
||
if not student_no:
|
||
return False
|
||
if len(student_no) < 4 or len(student_no) > 20:
|
||
return False
|
||
# 字母数字组合
|
||
return student_no.isalnum()
|
||
|
||
@staticmethod
|
||
def validate_phone(phone: str) -> bool:
|
||
"""验证手机号格式(中国手机号)"""
|
||
if not phone:
|
||
return False
|
||
pattern = r'^1[3-9]\d{9}$'
|
||
return bool(re.match(pattern, phone))
|
||
|
||
@staticmethod
|
||
def validate_points_change(points: int, max_abs: int = 100) -> tuple:
|
||
"""
|
||
验证分值变动
|
||
返回: (是否有效, 错误信息)
|
||
"""
|
||
if points == 0:
|
||
return False, "分值不能为0"
|
||
if abs(points) > max_abs:
|
||
return False, f"单次分值变动不能超过{max_abs}分"
|
||
return True, ""
|
||
|
||
|
||
# 单例导出
|
||
security = SecurityUtils() |