- 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)
61 lines
1.8 KiB
Python
61 lines
1.8 KiB
Python
import os
|
||
from django.conf import settings
|
||
|
||
|
||
def set_auth_cookies(response, access_token=None, refresh_token=None):
|
||
"""
|
||
为响应设置 HttpOnly Cookie(杜绝 XSS 窃取 token)
|
||
- access_token: HttpOnly, SameSite=Lax, Path=/
|
||
- refresh_token: HttpOnly, SameSite=Lax, Path=/
|
||
在生产/非 DEBUG 开启 Secure,本地开发/内网环境保持 Secure=False。
|
||
"""
|
||
if not response:
|
||
return response
|
||
|
||
debug_mode = getattr(settings, 'DJANGO_DEBUG', settings.DEBUG)
|
||
if isinstance(debug_mode, str):
|
||
debug_mode = debug_mode.lower() in ('true', '1', 'yes')
|
||
secure = not debug_mode
|
||
samesite = 'Lax'
|
||
|
||
jwt_settings = getattr(settings, 'SIMPLE_JWT', {})
|
||
|
||
if access_token:
|
||
access_lifetime = jwt_settings.get('ACCESS_TOKEN_LIFETIME')
|
||
max_age = int(access_lifetime.total_seconds()) if access_lifetime else 7 * 86400
|
||
response.set_cookie(
|
||
key='access_token',
|
||
value=str(access_token),
|
||
max_age=max_age,
|
||
httponly=True,
|
||
samesite=samesite,
|
||
secure=secure,
|
||
path='/'
|
||
)
|
||
|
||
if refresh_token:
|
||
refresh_lifetime = jwt_settings.get('REFRESH_TOKEN_LIFETIME')
|
||
max_age = int(refresh_lifetime.total_seconds()) if refresh_lifetime else 30 * 86400
|
||
response.set_cookie(
|
||
key='refresh_token',
|
||
value=str(refresh_token),
|
||
max_age=max_age,
|
||
httponly=True,
|
||
samesite=samesite,
|
||
secure=secure,
|
||
path='/'
|
||
)
|
||
|
||
return response
|
||
|
||
|
||
def clear_auth_cookies(response):
|
||
"""
|
||
清除认证 HttpOnly Cookie
|
||
"""
|
||
if not response:
|
||
return response
|
||
response.delete_cookie('access_token', path='/')
|
||
response.delete_cookie('refresh_token', path='/')
|
||
return response
|