From 87643aeb6e064876bbf337a545004e5e3be48698 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 22 Aug 2026 01:06:55 +0800 Subject: [PATCH] =?UTF-8?q?fix(auth):=20simplejwt=20blacklist=20=E9=80=82?= =?UTF-8?q?=E9=85=8D=E9=9D=9E=20auth=20User=EF=BC=9B=E9=83=A8=E7=BD=B2=20o?= =?UTF-8?q?ld2/old3=20=E5=AD=90=E5=9F=9F=E4=B8=8E=20SPA=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tokens.py 全局 patch RefreshToken.for_user:不写 OutstandingToken、UserUUID 转 str, 修复登录 500(OutstandingToken.user 必须为 auth.User 实例);settings/urls 移除 token_blacklist - core/views 新增 old2/old3 子域配置(走 _base 模板渲染),SPA fallback 支持 /index.html, .html 重定向排除目录请求,修复 invite 子域 302 错跳 - TempLate/index.html 同步 GVSite 最新构建产物(含头像下拉复用 GERP 组件) --- TempLate/index.html | 6 +++--- backup/www/index.html | 8 ++++---- gvsds/apps/core/views.py | 10 ++++++++-- gvsds/settings.py | 3 +-- gvsds/tokens.py | 42 ++++++++++++++++++++++++++++++++++++++++ gvsds/urls.py | 2 ++ 6 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 gvsds/tokens.py diff --git a/TempLate/index.html b/TempLate/index.html index e13be206..7a4b4866 100644 --- a/TempLate/index.html +++ b/TempLate/index.html @@ -3,10 +3,10 @@ - 银河锋锐软件开发工作室 + 银河锋锐(GVSDS) - - + +
diff --git a/backup/www/index.html b/backup/www/index.html index 761f500c..023ef771 100644 --- a/backup/www/index.html +++ b/backup/www/index.html @@ -3,10 +3,10 @@ - 银河锋锐软件开发工作室 - - - + 银河锋锐(GVSDS) + + +
diff --git a/gvsds/apps/core/views.py b/gvsds/apps/core/views.py index 97088ccf..56f3bd87 100644 --- a/gvsds/apps/core/views.py +++ b/gvsds/apps/core/views.py @@ -62,6 +62,8 @@ SUBDOMAIN_CONFIG = { 'vu': {'dir': None, 'root_is_index': False}, 'links': {'dir': None, 'root_is_index': False}, 'unclear': {'dir': 'Unclear', 'root_is_index': True}, + 'old2': {'dir': 'Olds/2', 'root_is_index': True, 'base': 'Olds/2/_base'}, + 'old3': {'dir': 'Olds/3', 'root_is_index': True, 'base': 'Olds/3/_base'}, } @@ -269,7 +271,8 @@ class SubdomainView(View): # www/invite 子域:旧 .html 页面已被 Vue SPA 取代,去掉后缀重定向到 Vue 路由 # 兼容历史链接(如 /login.html、/gallery.html 等被外部页面/脚本引用的旧地址) - if subdomain in ('www', 'invite') and file_path.endswith('.html'): + # 目录请求(/xxx/ 拼成的 /xxx/index.html)不走旧 .html 链接重定向,交给下方 SPA fallback + if subdomain in ('www', 'invite') and file_path.endswith('.html') and not file_path.endswith('/index.html'): new_path = file_path[:-5] or '/' redirect_url = new_path if request.META.get('QUERY_STRING'): @@ -278,7 +281,10 @@ class SubdomainView(View): # SPA fallback:前端单页应用(如 GERP Vue)的客户端路由路径(无扩展名) # 找不到对应文件时,回退到 index.html,由前端路由处理 - if config.get('root_is_index') and '.' not in os.path.basename(file_path): + # 目录请求(/xxx/ 被拼成 /xxx/index.html)同样回退 SPA,避免误入 Django 404 + if config.get('root_is_index') and ( + '.' not in os.path.basename(file_path) or file_path.endswith('/index.html') + ): index_resolved = _resolve_file(subdomain, 'index.html') if index_resolved: return _serve_file(index_resolved, subdomain) diff --git a/gvsds/settings.py b/gvsds/settings.py index 73186b5c..71c7a226 100644 --- a/gvsds/settings.py +++ b/gvsds/settings.py @@ -50,7 +50,6 @@ 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', @@ -295,7 +294,7 @@ SIMPLE_JWT = { '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=True), # 轮换后旧 refresh 立即失效 + 'BLACKLIST_AFTER_ROTATION': _app_getbool('jwt', 'BLACKLIST_AFTER_ROTATION', fallback=False), # 已禁用 blacklist(业务 User 非 Django auth 模型,OutstandingToken 无法落库) 'AUTH_HEADER_TYPES': tuple(_app_getlist('jwt', 'AUTH_HEADER_TYPES', fallback=['Bearer'])), 'USER_ID_FIELD': 'UserUUID', 'USER_ID_CLAIM': 'UserUUID', diff --git a/gvsds/tokens.py b/gvsds/tokens.py new file mode 100644 index 00000000..ec2d00d4 --- /dev/null +++ b/gvsds/tokens.py @@ -0,0 +1,42 @@ +"""simplejwt token 适配层 + +业务用户模型 core.User 非 Django auth 模型(项目未设置 AUTH_USER_MODEL), +simplejwt blacklist 的 OutstandingToken.user(FK -> auth.User)无法承接 core.User +实例,登录签发 token 时触发 ValueError。 +此处将 blacklist 相关操作全部 no-op,并全局替换 RefreshToken.for_user, +签发时不再落 OutstandingToken 黑名单表。 +""" + +from rest_framework_simplejwt.settings import api_settings +from rest_framework_simplejwt.tokens import RefreshToken as _BaseRefreshToken + + +def _for_user(cls, user): + """签发 refresh token(写 jti/exp),但不落 OutstandingToken 黑名单表""" + token = cls() + token[api_settings.USER_ID_CLAIM] = str(getattr(user, api_settings.USER_ID_FIELD)) # UUIDObj 需转 str 才能 JSON 序列化 + token.set_jti() + token.set_exp() + return token + + +class RefreshToken(_BaseRefreshToken): + """业务版 RefreshToken:blacklist 相关操作全部 no-op""" + + for_user = classmethod(_for_user) + + def check_blacklist(self): + return None + + def blacklist(self): + return None + + def outstand(self): + return None + + +# 全局替换 simplejwt 原版类方法,覆盖 TokenObtainPairView / TokenRefreshView 等内部调用 +_BaseRefreshToken.for_user = classmethod(_for_user) +_BaseRefreshToken.check_blacklist = lambda self: None +_BaseRefreshToken.blacklist = lambda self: None +_BaseRefreshToken.outstand = lambda self: None \ No newline at end of file diff --git a/gvsds/urls.py b/gvsds/urls.py index ceaf7422..319d33f2 100644 --- a/gvsds/urls.py +++ b/gvsds/urls.py @@ -1,3 +1,5 @@ +import gvsds.tokens # noqa: F401 # simplejwt blacklist 适配(签发 token 不落 OutstandingToken 表) + from django.conf import settings from django.urls import path, include, re_path from django.contrib import admin