security.py 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. """
  2. 安全相关功能
  3. """
  4. # 临时兼容:部分 bcrypt 版本缺少 __about__.__version__,导致 passlib 初始化失败
  5. # 在导入 passlib 前补充该属性,避免登录/解绑失败
  6. try:
  7. import bcrypt # type: ignore
  8. if not hasattr(bcrypt, "__about__"):
  9. class _BcryptAbout:
  10. __version__ = getattr(bcrypt, "__version__", "unknown")
  11. bcrypt.__about__ = _BcryptAbout() # type: ignore[attr-defined]
  12. except Exception:
  13. pass
  14. try:
  15. from passlib.context import CryptContext
  16. pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
  17. USE_PASSLIB = True
  18. except Exception:
  19. # 如果 passlib 有问题,使用 bcrypt 直接验证
  20. import bcrypt
  21. USE_PASSLIB = False
  22. try:
  23. from jose import JWTError, jwt
  24. except ImportError:
  25. # 如果没有jose,使用简单的实现
  26. import jwt
  27. JWTError = Exception
  28. from datetime import datetime, timedelta, timezone
  29. from typing import Optional
  30. from app.config import settings
  31. def verify_password(plain_password: str, hashed_password: str) -> bool:
  32. """验证密码"""
  33. if USE_PASSLIB:
  34. try:
  35. return pwd_context.verify(plain_password, hashed_password)
  36. except Exception:
  37. # 如果 passlib 验证失败,尝试使用 bcrypt 直接验证
  38. import bcrypt
  39. try:
  40. return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
  41. except Exception:
  42. return False
  43. else:
  44. # 直接使用 bcrypt
  45. import bcrypt
  46. try:
  47. return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
  48. except Exception:
  49. return False
  50. def get_password_hash(password: str) -> str:
  51. """获取密码哈希"""
  52. if USE_PASSLIB:
  53. try:
  54. return pwd_context.hash(password)
  55. except Exception:
  56. # 如果 passlib 失败,使用 bcrypt 直接生成
  57. import bcrypt
  58. salt = bcrypt.gensalt()
  59. hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
  60. return hashed.decode('utf-8')
  61. else:
  62. # 直接使用 bcrypt
  63. import bcrypt
  64. salt = bcrypt.gensalt()
  65. hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
  66. return hashed.decode('utf-8')
  67. def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
  68. """创建访问令牌"""
  69. to_encode = data.copy()
  70. # 使用北京时间 (UTC+8)
  71. beijing_tz = timezone(timedelta(hours=8))
  72. now = datetime.now(beijing_tz)
  73. if expires_delta:
  74. expire = now + expires_delta
  75. else:
  76. expire = now + timedelta(hours=24)
  77. to_encode.update({"exp": expire})
  78. encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm="HS256")
  79. return encoded_jwt
  80. def verify_token(token: str) -> Optional[dict]:
  81. """验证令牌"""
  82. try:
  83. payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
  84. return payload
  85. except JWTError:
  86. return None