76 lines
2.0 KiB
Python
76 lines
2.0 KiB
Python
"""
|
||
登录鉴权工具(JWT + 密码哈希)
|
||
|
||
设计目标:
|
||
- 最小依赖:密码哈希用 pbkdf2_hmac(标准库)
|
||
- JWT 使用 python-jose(已加入 requirements)
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import base64
|
||
import hashlib
|
||
import hmac
|
||
import os
|
||
import time
|
||
from typing import Any, Dict, Optional
|
||
|
||
from jose import jwt # type: ignore
|
||
|
||
|
||
def _jwt_secret() -> str:
|
||
s = (os.getenv("ATS_JWT_SECRET") or os.getenv("JWT_SECRET") or "").strip()
|
||
if s:
|
||
return s
|
||
# 允许开发环境兜底,但线上务必配置
|
||
return "dev-secret-change-me"
|
||
|
||
|
||
def jwt_encode(payload: Dict[str, Any], exp_sec: int = 3600) -> str:
|
||
now = int(time.time())
|
||
body = dict(payload or {})
|
||
body["iat"] = now
|
||
body["exp"] = now + int(exp_sec)
|
||
return jwt.encode(body, _jwt_secret(), algorithm="HS256")
|
||
|
||
|
||
def jwt_decode(token: str) -> Dict[str, Any]:
|
||
return jwt.decode(token, _jwt_secret(), algorithms=["HS256"])
|
||
|
||
|
||
def _b64(b: bytes) -> str:
|
||
return base64.urlsafe_b64encode(b).decode("utf-8").rstrip("=")
|
||
|
||
|
||
def _b64d(s: str) -> bytes:
|
||
s = (s or "").strip()
|
||
s = s + ("=" * (-len(s) % 4))
|
||
return base64.urlsafe_b64decode(s.encode("utf-8"))
|
||
|
||
|
||
def hash_password(password: str, iterations: int = 260_000) -> str:
|
||
"""
|
||
PBKDF2-SHA256:返回格式
|
||
pbkdf2_sha256$<iterations>$<salt_b64>$<hash_b64>
|
||
"""
|
||
pw = (password or "").encode("utf-8")
|
||
salt = os.urandom(16)
|
||
dk = hashlib.pbkdf2_hmac("sha256", pw, salt, int(iterations))
|
||
return f"pbkdf2_sha256${int(iterations)}${_b64(salt)}${_b64(dk)}"
|
||
|
||
|
||
def verify_password(password: str, password_hash: str) -> bool:
|
||
try:
|
||
s = str(password_hash or "")
|
||
if not s.startswith("pbkdf2_sha256$"):
|
||
return False
|
||
_, it_s, salt_b64, dk_b64 = s.split("$", 3)
|
||
it = int(it_s)
|
||
salt = _b64d(salt_b64)
|
||
dk0 = _b64d(dk_b64)
|
||
dk1 = hashlib.pbkdf2_hmac("sha256", (password or "").encode("utf-8"), salt, it)
|
||
return hmac.compare_digest(dk0, dk1)
|
||
except Exception:
|
||
return False
|
||
|