From e76a5b359ee6e7655f720cb83c0c8ceff909fca2 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 13 Aug 2026 17:55:54 +0800 Subject: [PATCH] =?UTF-8?q?security(jwt):=20=E6=94=B6=E6=95=9B=20token=20?= =?UTF-8?q?=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=EF=BC=88168h=E2=86=9224h?= =?UTF-8?q?=EF=BC=89=E5=B9=B6=E5=90=AF=E7=94=A8=20refresh=20=E8=BD=AE?= =?UTF-8?q?=E6=8D=A2=E9=BB=91=E5=90=8D=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ACCESS_TOKEN_LIFETIME 默认 24h(缩小泄露窗口) - BLACKLIST_AFTER_ROTATION 默认开启,注册 token_blacklist app - LOGGING 增加 RotatingFileHandler 落盘(django.log) - USE_TZ=False 决策文档化(存量 naive UTC 时间戳无法安全切换) --- gvsds/settings.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/gvsds/settings.py b/gvsds/settings.py index 64476eb5..73186b5c 100644 --- a/gvsds/settings.py +++ b/gvsds/settings.py @@ -50,6 +50,7 @@ INSTALLED_APPS = _app_getlist('apps', 'INSTALLED_APPS', fallback=[ 'django.contrib.staticfiles', 'rest_framework', 'rest_framework_simplejwt', + 'rest_framework_simplejwt.token_blacklist', # refresh 轮换后旧 token 拉黑(BLACKLIST_AFTER_ROTATION) 'corsheaders', 'gvsds.apps.core', 'gvsds.apps.gerp', @@ -235,6 +236,9 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = _app_get('server', 'LANGUAGE_CODE', fallback='zh-hans') TIME_ZONE = _app_get('server', 'TIME_ZONE', fallback='Asia/Shanghai') USE_I18N = _app_getbool('server', 'USE_I18N', fallback=True) +# USE_TZ 保持 False:全项目时间戳以 datetime.utcnow() 存 naive UTC, +# 改 True 会把存量 naive 时间解释为本地时区,导致 stats 周期键/跨时区比较错乱。 +# 若未来统一时区,需先迁移存量数据为 aware UTC(见架构债治理清单 2.7)。 USE_TZ = _app_getbool('server', 'USE_TZ', fallback=False) STATIC_URL = _app_get('static', 'STATIC_URL', fallback='/static/') @@ -288,10 +292,10 @@ REST_FRAMEWORK = { } SIMPLE_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(hours=_app_getint('jwt', 'ACCESS_TOKEN_LIFETIME_HOURS', fallback=168)), # 7 天 + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=_app_getint('jwt', 'ACCESS_TOKEN_LIFETIME_HOURS', fallback=24)), # 24 小时(收敛自 168h,缩小泄露窗口) 'REFRESH_TOKEN_LIFETIME': timedelta(days=_app_getint('jwt', 'REFRESH_TOKEN_LIFETIME_DAYS', fallback=90)), # 90 天 'ROTATE_REFRESH_TOKENS': _app_getbool('jwt', 'ROTATE_REFRESH_TOKENS', fallback=True), - 'BLACKLIST_AFTER_ROTATION': _app_getbool('jwt', 'BLACKLIST_AFTER_ROTATION', fallback=False), + 'BLACKLIST_AFTER_ROTATION': _app_getbool('jwt', 'BLACKLIST_AFTER_ROTATION', fallback=True), # 轮换后旧 refresh 立即失效 'AUTH_HEADER_TYPES': tuple(_app_getlist('jwt', 'AUTH_HEADER_TYPES', fallback=['Bearer'])), 'USER_ID_FIELD': 'UserUUID', 'USER_ID_CLAIM': 'UserUUID', @@ -330,9 +334,17 @@ LOGGING = { 'class': 'logging.StreamHandler', 'formatter': 'verbose', }, + 'file': { + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': str(BASE_DIR / 'logs' / 'django.log'), + 'maxBytes': 10 * 1024 * 1024, + 'backupCount': 5, + 'formatter': 'verbose', + 'encoding': 'utf-8', + }, }, 'root': { - 'handlers': ['console'], + 'handlers': ['console', 'file'], 'level': _app_get('logging', 'LEVEL', fallback='INFO'), }, }