调整文档路径

This commit is contained in:
2026-09-02 17:31:52 +08:00
parent 01b3798e02
commit 674ff09db1
43 changed files with 0 additions and 0 deletions
View File
+37
View File
@@ -0,0 +1,37 @@
"""配置:环境变量读取"""
import os
class Settings:
AUTH_DB_HOST: str = os.getenv("AUTH_DB_HOST", "10.100.154.100")
AUTH_DB_PORT: int = int(os.getenv("AUTH_DB_PORT", "5432"))
AUTH_DB_USER: str = os.getenv("AUTH_DB_USER", "postgres")
AUTH_DB_PASSWORD: str = os.getenv("AUTH_DB_PASSWORD", "digiwin")
AUTH_DB_NAME: str = os.getenv("AUTH_DB_NAME", "mcp_auth")
# 鼎捷云 IAM 配置
IAM_BASE_URL: str = os.getenv("IAM_BASE_URL", "https://iam.digiwincloud.com.cn")
# 应用 apptoken(digi-middleware-auth-app)
IAM_APP_TOKEN: str = os.getenv(
"IAM_APP_TOKEN",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImRhdGEtYnVzaW5lc3MtZGVtbyIsInNpZCI6MH0.Spo64LstbWxjYNefVFAbEbgfjzZoQGNcqKSGuYUOCRk",
)
# IAM token 校验缓存秒数
IAM_CACHE_TTL: int = int(os.getenv("IAM_CACHE_TTL", "30"))
# 前端静态文件目录(Docker 构建后注入)
STATIC_DIR: str = os.getenv("STATIC_DIR", "../frontend/dist")
# Redis 配置
REDIS_HOST: str = os.getenv("REDIS_HOST", "10.100.154.100")
REDIS_PORT: int = int(os.getenv("REDIS_PORT", "6379"))
REDIS_DB: int = int(os.getenv("REDIS_DB", "0"))
REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD", "digiwin")
# 反向代理路径前缀(让 Swagger / OpenAPI 在带前缀的代理后正确生成 URL)
# 生产经外层 nginx 暴露在 https://<host>/mcp-auth-api/ 下时设为 /mcp-auth-api;直连 8003 时留空
ROOT_PATH: str = os.getenv("ROOT_PATH", "")
settings = Settings()
+28
View File
@@ -0,0 +1,28 @@
"""鉴权库连接池"""
import asyncpg
from .config import settings
_pool: asyncpg.Pool | None = None
async def get_pool() -> asyncpg.Pool:
global _pool
if _pool is None:
_pool = await asyncpg.create_pool(
host=settings.AUTH_DB_HOST,
port=settings.AUTH_DB_PORT,
user=settings.AUTH_DB_USER,
password=settings.AUTH_DB_PASSWORD,
database=settings.AUTH_DB_NAME,
min_size=2,
max_size=10,
)
return _pool
async def close_pool() -> None:
global _pool
if _pool is not None:
await _pool.close()
_pool = None
+33
View File
@@ -0,0 +1,33 @@
"""FastAPI 依赖:IAM token 校验,提取当前管理员
从请求头 digi-middleware-auth-user / digi-middleware-auth-app 读取凭证,
调用 IAM /api/iam/v2/identity/token/analyze 校验并解析用户信息。
"""
from fastapi import Header, HTTPException, status
from . import iam
async def current_admin(
user_token: str | None = Header(None, alias="digi-middleware-auth-user"),
app_token: str | None = Header(None, alias="digi-middleware-auth-app"),
) -> dict:
if not user_token:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "未提供 IAM userToken")
info = await iam.analyze_token(user_token, app_token)
if info is None:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "IAM token 无效或已过期")
# 返回统一的管理员信息(兼容原有 admin.get("username") 调用)
return {
"username": info.get("name") or info.get("id") or "unknown",
"userId": info.get("id"),
"name": info.get("name"),
"email": info.get("email"),
"telephone": info.get("telephone"),
"tenantId": info.get("tenantId"),
"tenantName": info.get("tenantName"),
"raw": info,
}
+80
View File
@@ -0,0 +1,80 @@
"""鼎捷云 IAM token 鉴权服务
通过调用 IAM `/api/iam/v2/identity/token/analyze` 校验请求头中的
digi-middleware-auth-user / digi-middleware-auth-app,解析出用户信息。
进程内 LRU 缓存(userToken -> userInfo),TTL 由 IAM_CACHE_TTL 控制,
减少对 IAM 的重复调用。
"""
import time
import httpx
from .config import settings
# httpx 异步客户端(进程级单例,复用连接池)
_http_client: httpx.AsyncClient | None = None
# 缓存:userToken -> (user_info, fetched_at)
_cache: dict[str, tuple[dict | None, float]] = {}
def _get_http_client() -> httpx.AsyncClient:
global _http_client
if _http_client is None:
_http_client = httpx.AsyncClient(timeout=5.0)
return _http_client
async def close_iam_client() -> None:
"""关闭 httpx 客户端(进程退出时调用)。"""
global _http_client
if _http_client is not None:
await _http_client.aclose()
_http_client = None
async def analyze_token(user_token: str, app_token: str | None = None) -> dict | None:
"""校验 IAM userToken,返回用户信息 dict 或 None。
调用 POST {IAM_BASE_URL}/api/iam/v2/identity/token/analyze,
请求头携带 digi-middleware-auth-app 与 digi-middleware-auth-user。
成功时返回包含 id/name/tenantId/tenantName 等字段的 dict。
"""
# 1. 查缓存
now = time.time()
cached = _cache.get(user_token)
if cached is not None and (now - cached[1]) < settings.IAM_CACHE_TTL:
return cached[0]
# 2. 调用 IAM analyze
headers = {
"digi-middleware-auth-user": user_token,
"digi-middleware-auth-app": app_token or settings.IAM_APP_TOKEN,
}
try:
client = _get_http_client()
resp = await client.post(
f"{settings.IAM_BASE_URL}/api/iam/v2/identity/token/analyze",
headers=headers,
)
except Exception as ex:
# IAM 不可达,缓存短时间避免雪崩
_cache[user_token] = (None, now)
print(f"[IAM] analyze 请求异常: {ex}")
return None
if resp.status_code == 200:
data = resp.json()
# 兼容字段:id(用户标识)/ name(姓名)
if data.get("id") or data.get("name"):
_cache[user_token] = (data, now)
return data
# 返回体无用户标识,视为无效
_cache[user_token] = (None, now)
return None
# 非 200(token 无效/过期),缓存避免雪崩
_cache[user_token] = (None, now)
return None
+73
View File
@@ -0,0 +1,73 @@
"""Redis 连接池 + 缓存读写封装
用于 verify-token 的服务/Token 缓存,减少数据库访问。
缓存 Key 约定:
mcp:svc:{api_key_hash} → 服务信息(TTL 120s)
mcp:tok:{token_hash} → Token 信息(TTL 30s)
mcp:tok:miss:{token_hash} → 无效标记,防穿透(TTL 30s)
"""
import json
import redis.asyncio as aioredis
from .config import settings
_pool: aioredis.Redis | None = None
async def get_redis() -> aioredis.Redis:
"""获取 Redis 连接(进程级单例)。"""
global _pool
if _pool is None:
_pool = aioredis.Redis(
host=settings.REDIS_HOST,
port=settings.REDIS_PORT,
db=settings.REDIS_DB,
password=settings.REDIS_PASSWORD,
decode_responses=True,
)
return _pool
async def close_redis() -> None:
"""关闭 Redis 连接(进程退出时调用)。"""
global _pool
if _pool is not None:
await _pool.aclose()
_pool = None
async def cache_get(key: str) -> dict | None:
"""读取 JSON 缓存,返回 dict 或 None。Redis 不可用时返回 None(视为缓存未命中)。"""
try:
r = await get_redis()
raw = await r.get(key)
except Exception:
return None
if raw is None:
return None
try:
return json.loads(raw)
except Exception:
return None
async def cache_set(key: str, value: dict, ttl: int) -> None:
"""写入 JSON 缓存,带 TTL(秒)。Redis 不可用时静默跳过。"""
try:
r = await get_redis()
await r.setex(key, ttl, json.dumps(value))
except Exception:
pass
async def cache_delete(*keys: str) -> None:
"""删除缓存 key。Redis 不可用时静默跳过,缓存会自然过期。"""
if not keys:
return
try:
r = await get_redis()
await r.delete(*keys)
except Exception:
pass