| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- """JWT工具 - 标准RFC7519格式 + Redis持久化Refresh Token"""
- import os
- import secrets
- import jwt
- from datetime import datetime, timedelta, timezone
- _ISSUER = "trip-planner-pro"
- # 密钥(首次运行自动生成)
- _SECRET_KEY = None
- def _get_secret() -> str:
- global _SECRET_KEY
- if _SECRET_KEY is None:
- key = os.getenv("JWT_SECRET")
- if not key:
- key = os.urandom(32).hex()
- os.environ["JWT_SECRET"] = key
- _SECRET_KEY = key
- return _SECRET_KEY
- ALGORITHM = "HS256"
- # 过期时间
- ACCESS_TOKEN_EXPIRE_MINUTES = 30
- REFRESH_TOKEN_EXPIRE_DAYS = 7
- def _now() -> datetime:
- return datetime.now(timezone.utc)
- def create_access_token(user_id: int) -> str:
- """生成标准 Access Token(30分钟有效,HttpOnly Cookie传递)"""
- now = _now()
- payload = {
- "iss": _ISSUER,
- "sub": str(user_id),
- "aud": f"{_ISSUER}/api",
- "exp": now + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
- "iat": now,
- "jti": secrets.token_hex(16),
- "type": "access",
- }
- return jwt.encode(payload, _get_secret(), algorithm=ALGORITHM)
- def create_refresh_token(user_id: int) -> tuple:
- """
- 生成标准 Refresh Token(7天有效,jti存入Redis)
- 返回: (token_str, jti)
- """
- now = _now()
- jti = secrets.token_hex(16)
- payload = {
- "iss": _ISSUER,
- "sub": str(user_id),
- "aud": f"{_ISSUER}/auth/refresh",
- "exp": now + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS),
- "iat": now,
- "jti": jti,
- "type": "refresh",
- }
- token = jwt.encode(payload, _get_secret(), algorithm=ALGORITHM)
- return token, jti
- def verify_access_token(token: str) -> dict:
- """验证 Access Token,返回 {"id": user_id}"""
- payload = jwt.decode(
- token,
- _get_secret(),
- algorithms=[ALGORITHM],
- audience=f"{_ISSUER}/api",
- issuer=_ISSUER,
- options={"require": ["exp", "iat", "sub", "jti", "type"]},
- )
- if payload.get("type") != "access":
- raise jwt.InvalidTokenError("Token类型错误")
- return {"id": int(payload["sub"])}
- def verify_refresh_token(token: str) -> dict:
- """验证 Refresh Token(仅JWT签名验证),返回 {"id": user_id, "jti": jti}"""
- payload = jwt.decode(
- token,
- _get_secret(),
- algorithms=[ALGORITHM],
- audience=f"{_ISSUER}/auth/refresh",
- issuer=_ISSUER,
- options={"require": ["exp", "iat", "sub", "jti", "type"]},
- )
- if payload.get("type") != "refresh":
- raise jwt.InvalidTokenError("Token类型错误")
- return {"id": int(payload["sub"]), "jti": payload["jti"]}
- def get_token_jti(token: str) -> str:
- """解码token获取jti(不验证签名,仅用于找回jti)"""
- try:
- payload = jwt.decode(token, options={"verify_signature": False})
- return payload.get("jti", "")
- except Exception:
- return ""
|