security.py 1.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. """密码哈希与 JWT 签发/校验"""
  2. from datetime import datetime, timedelta, timezone
  3. from typing import Any, Dict, Optional
  4. import bcrypt
  5. from jose import JWTError, jwt
  6. from app.core.config import get_settings
  7. def hash_password(plain: str) -> str:
  8. return bcrypt.hashpw(plain.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
  9. def verify_password(plain: str, password_hash: str) -> bool:
  10. try:
  11. return bcrypt.checkpw(plain.encode("utf-8"), password_hash.encode("utf-8"))
  12. except (ValueError, TypeError):
  13. return False
  14. def create_access_token(sub: str, extra: Optional[Dict[str, Any]] = None) -> str:
  15. settings = get_settings()
  16. payload: Dict[str, Any] = {"sub": sub}
  17. if extra:
  18. payload.update(extra)
  19. expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
  20. payload["exp"] = expire
  21. return jwt.encode(payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
  22. def decode_token(token: str) -> Dict[str, Any]:
  23. settings = get_settings()
  24. return jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
  25. class TokenError(Exception):
  26. """JWT 无效或过期。"""
  27. def safe_decode_token(token: str) -> Dict[str, Any]:
  28. try:
  29. return decode_token(token)
  30. except JWTError as exc:
  31. raise TokenError(str(exc)) from exc