feat(auth): 完成 AUTH-01~04 后端实现 - IP白名单中间件+mfa.py删除+agents.py重构+conftest修复
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 管理端 IP 白名单中间件(三端认证重构 AUTH-02)
|
||||
# =============================================================================
|
||||
# 说明:管理后台(/api/admin/*)的 IP 白名单校验中间件。
|
||||
#
|
||||
# 工作原理:
|
||||
# 1. 仅在 production 环境启用(非生产环境直接放行)
|
||||
# 2. 仅对管理端 API 路径生效(/api/admin/* 和 /api/auth/otp-admin-*)
|
||||
# 3. 校验客户端 IP 是否在 admin_allowed_ips 白名单内
|
||||
# 4. 不在白名单内则返回 403 + 统一错误格式 {code: 4004, message: "无访问权限"}
|
||||
#
|
||||
# 配置来源:
|
||||
# - APP_ENV=production 时启用(从环境变量读取)
|
||||
# - admin_allowed_ips 配置在 app.config(格式:"117.147.35.138,218.75.34.87,10.240.0.0/16")
|
||||
#
|
||||
# 使用方式:
|
||||
# 在 main.py 中注册:
|
||||
# from app.middleware.admin_ip_whitelist import AdminIPWhitelistMiddleware
|
||||
# app.add_middleware(AdminIPWhitelistMiddleware)
|
||||
# =============================================================================
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Callable
|
||||
|
||||
from fastapi import Request, Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
from app.utils.env_gating import is_production, ip_in_whitelist
|
||||
from app.utils.response import error_response
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 需要校验 IP 白名单的管理端路径前缀(正则表达式)
|
||||
ADMIN_PATH_PATTERNS = [
|
||||
r"^/api/admin/", # 管理后台 API
|
||||
r"^/api/auth/otp-admin", # 管理端 OTP 操作
|
||||
]
|
||||
|
||||
|
||||
class AdminIPWhitelistMiddleware(BaseHTTPMiddleware):
|
||||
"""管理端 IP 白名单校验中间件。
|
||||
|
||||
仅在 production 环境对管理端 API 路径生效。
|
||||
非生产环境(dev/test)直接放行,方便本地开发。
|
||||
"""
|
||||
|
||||
def __init__(self, app, *args, **kwargs):
|
||||
super().__init__(app, *args, **kwargs)
|
||||
# 预编译正则表达式,提升匹配性能
|
||||
self._compiled_patterns = [re.compile(p) for p in ADMIN_PATH_PATTERNS]
|
||||
|
||||
def _is_admin_path(self, path: str) -> bool:
|
||||
"""判断路径是否属于管理端 API。
|
||||
|
||||
Args:
|
||||
path: 请求路径(如 "/api/admin/users")
|
||||
|
||||
Returns:
|
||||
bool: True=管理端路径,False=非管理端路径
|
||||
"""
|
||||
for pattern in self._compiled_patterns:
|
||||
if pattern.match(path):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
||||
"""中间件主逻辑。
|
||||
|
||||
1. 非 production 环境 → 直接放行
|
||||
2. 非管理端路径 → 直接放行
|
||||
3. 获取客户端 IP → 白名单校验
|
||||
- 在白名单 → 放行
|
||||
- 不在白名单 → 返回 403
|
||||
"""
|
||||
# 1. 非 production 环境 → 直接放行(环境门控)
|
||||
if not is_production():
|
||||
return await call_next(request)
|
||||
|
||||
# 2. 非管理端路径 → 直接放行
|
||||
path = request.url.path
|
||||
if not self._is_admin_path(path):
|
||||
return await call_next(request)
|
||||
|
||||
# 3. 获取客户端 IP
|
||||
# 优先从 X-Forwarded-For 获取(反向代理场景)
|
||||
# 否则使用 request.client.host
|
||||
client_ip = self._get_client_ip(request)
|
||||
|
||||
logger.debug(f"管理端 IP 校验: path={path}, client_ip={client_ip}")
|
||||
|
||||
# 4. 白名单校验
|
||||
if not ip_in_whitelist(client_ip):
|
||||
logger.warning(
|
||||
f"管理端 IP 未授权: path={path}, client_ip={client_ip}, "
|
||||
f"拒绝访问"
|
||||
)
|
||||
# 返回统一错误格式(与 AppException 一致)
|
||||
return JSONResponse(
|
||||
status_code=200, # HTTP 状态码 200,业务错误码在 body 中
|
||||
content=error_response(
|
||||
code=4004,
|
||||
message="无访问权限:您的 IP 不在允许范围内,请联系管理员"
|
||||
),
|
||||
)
|
||||
|
||||
# 5. 在白名单内 → 放行
|
||||
return await call_next(request)
|
||||
|
||||
def _get_client_ip(self, request: Request) -> str:
|
||||
"""获取客户端真实 IP。
|
||||
|
||||
优先从 X-Forwarded-For 请求头获取(反向代理场景),
|
||||
这是标准的获取客户端真实 IP 的方式。
|
||||
|
||||
Args:
|
||||
request: FastAPI 请求对象
|
||||
|
||||
Returns:
|
||||
str: 客户端 IP 地址
|
||||
"""
|
||||
# X-Forwarded-For 可能包含多个 IP,第一个是原始客户端
|
||||
forwarded_for = request.headers.get("X-Forwarded-For")
|
||||
if forwarded_for:
|
||||
# 格式:"client_ip, proxy1, proxy2"
|
||||
# 取第一个(原始客户端)
|
||||
ips = forwarded_for.split(",")
|
||||
if ips:
|
||||
return ips[0].strip()
|
||||
|
||||
# 直接连接场景
|
||||
if request.client:
|
||||
return request.client.host
|
||||
|
||||
# 兜底
|
||||
return ""
|
||||
Reference in New Issue
Block a user