refactor: 迁移本地 Supabase 到云端,使用 JWKS 进行 JWT 验证
- 新增 JwtVerifier 支持 RS256 + JWKS 验证 - 简化 docker-compose,删除本地 Supabase 服务(kong/auth/storage等) - 删除冗余的 Supabase 配置文件(volumes目录) - 适配测试用例以支持新配置方式 - 更新运行时文档和迁移计划
This commit is contained in:
@@ -3,10 +3,14 @@ from __future__ import annotations
|
||||
from typing import Annotated
|
||||
from uuid import UUID
|
||||
|
||||
import jwt
|
||||
from fastapi import Depends, Header, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.auth.jwt_verifier import (
|
||||
JwtVerifier,
|
||||
TokenValidationError,
|
||||
TokenVerifierUnavailableError,
|
||||
)
|
||||
from core.auth.models import CurrentUser
|
||||
from core.config.settings import config
|
||||
from core.db import get_db
|
||||
@@ -18,6 +22,7 @@ from v1.users.service import AuthLookupAdapter, UserService
|
||||
logger = get_logger("v1.users.dependencies")
|
||||
|
||||
_auth_gateway: SupabaseAuthGateway | None = None
|
||||
_jwt_verifier: JwtVerifier | None = None
|
||||
|
||||
|
||||
def get_auth_gateway() -> SupabaseAuthGateway:
|
||||
@@ -27,6 +32,19 @@ def get_auth_gateway() -> SupabaseAuthGateway:
|
||||
return _auth_gateway
|
||||
|
||||
|
||||
def get_jwt_verifier() -> JwtVerifier:
|
||||
global _jwt_verifier
|
||||
if _jwt_verifier is None:
|
||||
jwks_url = config.supabase.jwks_url
|
||||
issuer = config.supabase.jwt_issuer
|
||||
audience = config.supabase.jwt_audience
|
||||
if not jwks_url or not issuer or not audience:
|
||||
logger.error("JWT validation failed: verifier config not configured")
|
||||
raise HTTPException(status_code=503, detail="JWT verifier not configured")
|
||||
_jwt_verifier = JwtVerifier(jwks_url=jwks_url, issuer=issuer, audience=audience)
|
||||
return _jwt_verifier
|
||||
|
||||
|
||||
def get_current_user(authorization: str | None = Header(default=None)) -> CurrentUser:
|
||||
if not authorization:
|
||||
logger.warning("JWT validation failed: missing authorization header")
|
||||
@@ -37,46 +55,17 @@ def get_current_user(authorization: str | None = Header(default=None)) -> Curren
|
||||
logger.warning("JWT validation failed: invalid authorization scheme")
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
|
||||
secret = config.supabase.jwt_secret
|
||||
if not secret:
|
||||
logger.error("JWT validation failed: secret not configured")
|
||||
raise HTTPException(status_code=503, detail="JWT secret not configured")
|
||||
|
||||
supabase_url = config.supabase.public_url.rstrip("/")
|
||||
expected_issuer = f"{supabase_url}/auth/v1"
|
||||
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
secret,
|
||||
algorithms=["HS256"],
|
||||
audience="authenticated",
|
||||
issuer=expected_issuer,
|
||||
options={
|
||||
"verify_aud": True,
|
||||
"verify_iss": True,
|
||||
"verify_exp": True,
|
||||
"require": ["sub", "aud", "iss", "exp"],
|
||||
},
|
||||
)
|
||||
except jwt.ExpiredSignatureError:
|
||||
logger.warning("JWT validation failed: token expired")
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
except jwt.InvalidAudienceError:
|
||||
logger.warning("JWT validation failed: invalid audience")
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
except jwt.InvalidIssuerError:
|
||||
logger.warning("JWT validation failed: invalid issuer")
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
except jwt.InvalidSignatureError:
|
||||
logger.warning("JWT validation failed: invalid signature")
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
except jwt.DecodeError:
|
||||
logger.warning("JWT validation failed: malformed token")
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
except jwt.PyJWTError as exc:
|
||||
payload = get_jwt_verifier().verify(token)
|
||||
except HTTPException:
|
||||
raise
|
||||
except TokenVerifierUnavailableError:
|
||||
logger.error("JWT validation failed: verifier unavailable")
|
||||
raise HTTPException(status_code=503, detail="JWT verifier unavailable")
|
||||
except TokenValidationError as exc:
|
||||
logger.warning(
|
||||
"JWT validation failed: unknown error", error_type=type(exc).__name__
|
||||
"JWT validation failed",
|
||||
error_type=type(exc).__name__,
|
||||
)
|
||||
raise HTTPException(status_code=401, detail="Unauthorized") from exc
|
||||
|
||||
|
||||
Reference in New Issue
Block a user