提交登录修改

This commit is contained in:
super
2026-05-23 18:32:45 +08:00
parent 2e2de02476
commit 1e087c1aae
52 changed files with 2139 additions and 1572 deletions

83
app/jwt_util.py Normal file
View File

@@ -0,0 +1,83 @@
"""
JWT 工具:解析 Java 端签发的 token、从请求中提取 token。
与 Java 端 JwtService 共用同一个 HS256 密钥AIIMAGE_JWT_SECRET
"""
import os
import sys
from typing import Optional, Tuple
from flask import request
try:
import jwt as pyjwt
_PYJWT_IMPORT_ERROR = None
except ImportError as _e:
pyjwt = None
_PYJWT_IMPORT_ERROR = _e
# 启动时立刻给出醒目提示,避免上线后才发现一直 401
print(
"[auth] WARNING: PyJWT 未安装,所有依赖 JWT 的接口都会返回 401。"
" 请执行 `pip install PyJWT==2.10.1` 或 `pip install -r requirements.txt`。",
file=sys.stderr,
)
_DEFAULT_SECRET = "please-change-this-secret-please-rotate-at-least-32-bytes"
COOKIE_NAME = os.getenv("AIIMAGE_AUTH_COOKIE_NAME", "aiimage_token")
def _signing_key() -> bytes:
"""与 Java JwtService.signingKey 保持一致UTF-8 字节,不足 32 字节右侧补 0。"""
secret = os.getenv("AIIMAGE_JWT_SECRET", _DEFAULT_SECRET)
key_bytes = secret.encode("utf-8")
if len(key_bytes) < 32:
key_bytes = key_bytes + b"\x00" * (32 - len(key_bytes))
return key_bytes
def parse_token_with_reason(token: str) -> Tuple[Optional[dict], Optional[str]]:
"""解析 JWT返回 (payload, error_reason)。
payload 命中时 error_reason 为 None失败时 payload 为 Noneerror_reason 描述根因。"""
if not token:
return None, "token 为空"
if pyjwt is None:
return None, f"PyJWT 未安装({_PYJWT_IMPORT_ERROR}"
# 打印 token 头,便于发现 alg 不是 HS256 等情况(不验签,仅 base64 解码 header
try:
header = pyjwt.get_unverified_header(token)
except Exception as e:
return None, f"无法解析 token header: {type(e).__name__}: {e}"
alg = header.get("alg") or "?"
try:
payload = pyjwt.decode(token, _signing_key(), algorithms=["HS256", "HS384", "HS512"])
except Exception as e:
# 常见ExpiredSignatureError / InvalidSignatureError / DecodeError / InvalidAlgorithmError
return None, f"{type(e).__name__}: {e} (token alg={alg})"
sub = payload.get("sub")
try:
user_id = int(sub) if sub is not None else None
except (TypeError, ValueError):
return None, f"sub 字段非法: {sub!r}"
if user_id is None:
return None, "payload 缺少 sub 字段"
return {
"user_id": user_id,
"username": payload.get("username") or "",
"device_id": payload.get("deviceId") or "",
}, None
def parse_token(token: str) -> Optional[dict]:
"""解析 JWT返回 {user_id, username, device_id};失败返回 None。"""
payload, _ = parse_token_with_reason(token)
return payload
def get_token_from_request() -> str:
"""优先从 Authorization: Bearer 取,其次从 cookie 取。"""
auth = request.headers.get("Authorization", "")
if auth.startswith("Bearer "):
token = auth[len("Bearer "):].strip()
if token:
return token
return (request.cookies.get(COOKIE_NAME) or "").strip()