fix(security): P1/P2 审计修复 + JWT HttpOnly Cookie 双模认证 + 限流
- P0/P1 审计修复: 滑块验证码不再下发 x_position/成败即销毁 key、 user-login 补失败计数+滑块门控、限流标识改 X-Real-IP、 百度翻译 appkey 环境化、ChangeEmail/ChangePhone 补调 avalidate、 logs/tasks.py Count(filter=Q) 修复、chat 收藏 SSRF 内网黑名单 - P1 #6/7: token_blacklist + ROTATE_REFRESH_TOKENS 开启, /user/token/refresh/ 挂载 - #2 JWT HttpOnly Cookie 双模认证: user/cookie_auth.py 种/清 Cookie, user/authentication.py CookieOrHeaderJWTAuthentication(Bearer 优先/_COOKIE 兜底), user/views/token.py CookieTokenRefreshView + UserLogoutAPIView(/user/logout/), create_standardized_response 自动对含 token 的响应种 Cookie, 异步视图内 RefreshToken.for_user 全部 sync_to_async 包裹(修 SynchronousOnlyOperation 500), WS ChatConsumer 优先读 Cookie token - P2 #11 限流: utils/rate_limit.py 固定窗口频控, shorturl 生成 匿名10次/分+登录60次/分, 邮箱验证码 同邮箱60s1次+同IP10次/10min, 登录/注册验证码 错5次作废+成功即销毁防重放, 换绑邮箱/手机 同步落地, urls.py 补挂 shorturl 路由(此前 404)
This commit is contained in:
@@ -353,10 +353,20 @@ class ChangeEmailSerializer(Serializer):
|
||||
if cached is None:
|
||||
raise ValidationError({'code': '验证码已过期,请重新获取'})
|
||||
if cached['code'] != code:
|
||||
raise ValidationError({'code': '验证码错误'})
|
||||
from utils.rate_limit import record_failure as _rl_record_failure
|
||||
from asgiref.sync import sync_to_async
|
||||
fails = await sync_to_async(_rl_record_failure)('change_email_vcode', str(user.id), max_failures=5, window_seconds=300)
|
||||
if fails >= 5:
|
||||
await adelete_cache(cache_key)
|
||||
raise ValidationError({'code': '验证码错误次数超限,已作废,请重新获取'})
|
||||
raise ValidationError({'code': f'验证码错误,还剩 {5 - fails} 次尝试机会'})
|
||||
if cached['email'] != new_email:
|
||||
raise ValidationError({'new_email': '邮箱与发送验证码时的邮箱不一致'})
|
||||
|
||||
from utils.rate_limit import reset_failures as _rl_reset_failures
|
||||
from asgiref.sync import sync_to_async
|
||||
await sync_to_async(_rl_reset_failures)('change_email_vcode', str(user.id))
|
||||
|
||||
return attrs
|
||||
|
||||
async def asave(self):
|
||||
@@ -458,10 +468,20 @@ class ChangePhoneSerializer(Serializer):
|
||||
if cached is None:
|
||||
raise ValidationError({'code': '验证码已过期,请重新获取'})
|
||||
if cached['code'] != code:
|
||||
raise ValidationError({'code': '验证码错误'})
|
||||
from utils.rate_limit import record_failure as _rl_record_failure
|
||||
from asgiref.sync import sync_to_async
|
||||
fails = await sync_to_async(_rl_record_failure)('change_phone_vcode', str(user.id), max_failures=5, window_seconds=300)
|
||||
if fails >= 5:
|
||||
await adelete_cache(cache_key)
|
||||
raise ValidationError({'code': '验证码错误次数超限,已作废,请重新获取'})
|
||||
raise ValidationError({'code': f'验证码错误,还剩 {5 - fails} 次尝试机会'})
|
||||
if cached['phone'] != new_phone:
|
||||
raise ValidationError({'new_phone': '手机号与发送验证码时的手机号不一致'})
|
||||
|
||||
from utils.rate_limit import reset_failures as _rl_reset_failures
|
||||
from asgiref.sync import sync_to_async
|
||||
await sync_to_async(_rl_reset_failures)('change_phone_vcode', str(user.id))
|
||||
|
||||
return attrs
|
||||
|
||||
async def asave(self):
|
||||
|
||||
Reference in New Issue
Block a user