merge: integrate latest qyqy_develop (auth login + RBAC read) into ZSY branch
Incremental merge on top ofef701c8, which already integrated the earlier qyqy basebbf623a. qyqy_develop only added commits on top ofbbf623a, so this merge is conflict-free. Incoming: account/password login (POST /api/v1/auth/tokens), RBAC read-only query API, rate limit dependency, login test console and user management tools. Additive changes in app/main.py, requirements.txt and pyproject.toml from both sides are all preserved. ZSY side capabilities (visitor tokens, customer service agent, knowledge retrieval, profile projection) are unchanged.
This commit is contained in:
@@ -69,3 +69,49 @@ async def enforce_rate_limit(
|
||||
f"请在 {retry_after_seconds} 秒后重试",
|
||||
retry_after_seconds,
|
||||
)
|
||||
|
||||
|
||||
#: 登录端点的限流参数。比普通接口严得多:普通接口的 `policy.max_requests` 是按"已登录用户
|
||||
#: 的操作频率"定的,而这里是**密码爆破**的入口,必须独立收紧。
|
||||
LOGIN_WINDOW_SECONDS = 60
|
||||
LOGIN_MAX_ATTEMPTS = 10
|
||||
LOGIN_COUNTER_PREFIX = "login"
|
||||
|
||||
|
||||
async def enforce_login_rate_limit(request: Request) -> None:
|
||||
"""登录端点专用的限流闸门:按客户端 IP,**不依赖认证上下文**。
|
||||
|
||||
为什么不能复用 `enforce_rate_limit`:它声明依赖 `build_request_context`
|
||||
(见本模块文档"顺序保证"),挂到登录端点就变成"要登录先登录"——登录请求本来
|
||||
就不带令牌。而爆破恰恰发生在**没有令牌**的时候,所以这里必须另立一个闸门。
|
||||
|
||||
维度取客户端 IP + 路由模板:拿不到 `RequestContext.user_id`(那时还没有身份),
|
||||
用 IP 是唯一可用的稳定维度;本地部署里所有客户端可能共用一个出口 IP,但登录
|
||||
端点的价值在于**挡住自动化爆破**,IP 维度足够,且不引入第二套鉴权解析。
|
||||
|
||||
降级与 `enforce_rate_limit` 一致:后端返回 `None`(Redis 不可用)时**放行**并告警,
|
||||
不因为限流组件故障把所有人挡在门外。
|
||||
"""
|
||||
policy = RateLimitPolicy.from_settings(get_settings())
|
||||
if not policy.enabled:
|
||||
return
|
||||
client = request.client.host if request.client is not None else "unknown"
|
||||
template = route_template(request)
|
||||
result = await get_counter_backend().increment(
|
||||
f"{LOGIN_COUNTER_PREFIX}:{client}:{request.method}:{template}",
|
||||
LOGIN_WINDOW_SECONDS,
|
||||
)
|
||||
if result is None:
|
||||
logger.warning("限流后端不可用,降级放行 route=%s", template)
|
||||
return
|
||||
count, retry_after_seconds = result
|
||||
if count > LOGIN_MAX_ATTEMPTS:
|
||||
logger.warning(
|
||||
"登录限流 route=%s ip=%s count=%s limit=%s",
|
||||
template, client, count, LOGIN_MAX_ATTEMPTS,
|
||||
)
|
||||
raise RateLimitExceededError(
|
||||
f"登录尝试过于频繁:每 {LOGIN_WINDOW_SECONDS} 秒最多 {LOGIN_MAX_ATTEMPTS} 次,"
|
||||
f"请在 {retry_after_seconds} 秒后重试",
|
||||
retry_after_seconds,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user