feat(BIZ-129): Flask-Limiter 集成 + 登录/重置限流

- requirements.txt: flask-limiter>=4.0
- app.py: 创建 Limiter 实例(memory:// 存储,60/分钟默认)
- auth_api.py:
  - 简化 register_auth_api(app, limiter=None)——limiter 仅作能力标记
  - _rate_limit(rate, methods) 装饰器:手写 IP+endpoint 滚动窗口限流
    (Flask-Limiter 在测试 client 下不生效,故手动实现;多进程需切换 Redis)
  - login / reset-request / reset-confirm 三个路由应用装饰器

限流策略(架构决策 #5/#6 配合):
  - /login: 5 次/分钟(IP 级爆破防御,配合账号级 5 次锁定 15min)
  - /reset-request: 3 次/分钟(防邮件/短信滥用)
  - /reset-confirm: 5 次/分钟(防 token 暴力猜解)

测试结果(test_client 压测):
  - login: 1-5 次业务 401,6 次起 429 ✅
  - reset-request: 1-3 次业务 200,4 次起 429 ✅

依赖:commit b002e92 的 7 路由蓝图。

BIZ-127 架构决策:
  ✓ #5 限流 5 次/分钟(IP 级,与账号级锁定 15min 双重防御)

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
costcodev
2026-09-05 07:13:50 +08:00
parent b002e929dc
commit 44f46a0e40
3 changed files with 108 additions and 8 deletions
+20 -1
View File
@@ -31,6 +31,25 @@ from lottery import DoubleColorBallGenerator
app = Flask(__name__)
# ============================================================
# Flask-Limiter 限流(BIZ-129 commit 2)
# ============================================================
# 架构决策:按 IP + 路径限流,内存存储(单进程;多进程需切换 Redis)
# - 默认 60 次/分钟
# - login / reset-* 路由单独在 auth_api.py 里装饰
try:
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
key_func=get_remote_address,
app=app,
default_limits=['60 per minute'],
storage_uri='memory://',
)
except ImportError as e:
print(f'[BIZ-129] Flask-Limiter 不可用,限流禁用:{e}', file=sys.stderr)
limiter = None
# ============================================================
# 认证 API 蓝图注册(BIZ-129 方案 C)
# ============================================================
@@ -38,7 +57,7 @@ app = Flask(__name__)
# reset-request / reset-confirm。session 存 sha256 hash 入库(架构决策 #2)。
try:
from auth_api import register_auth_api
register_auth_api(app)
register_auth_api(app, limiter=limiter)
except ImportError as e:
# auth_api.py 未就绪时不影响主流程(保持向后兼容)
print(f'[BIZ-129] auth_api 蓝图注册跳过:{e}', file=sys.stderr)