fix(auth): simplejwt blacklist 适配非 auth User;部署 old2/old3 子域与 SPA fallback

- 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 组件)
This commit is contained in:
TermiNexus committed 2026-08-22 01:06:55 +08:00
1 parent 21354faa44
commit 87643aeb6e
6 files changed
+60 -11

No files matched your search

+3 -3
View File
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>银河锋锐软件开发工作室</title>
<title>银河锋锐(GVSDS)</title>
<link rel="icon" href="https://hb.cdn.gvsds.com/assets/icon/ICON.png">
<script type="module" crossorigin src="/GVSite/index-D6E7GCNx.js"></script>
<link rel="stylesheet" crossorigin href="/GVSite/index-C4rcZEW_.css">
<script type="module" crossorigin src="/GVSite/index-CK-5nual.js"></script>
<link rel="stylesheet" crossorigin href="/GVSite/index-DRWk7H-y.css">
</head>
<body>
<div id="app"></div>
+4 -4
View File
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>银河锋锐软件开发工作室</title>
<link rel="icon" href="/assets/icon/ICON.png">
<script type="module" crossorigin src="/GVSite/index-DGlonf97.js"></script>
<link rel="stylesheet" crossorigin href="/GVSite/index-C4rcZEW_.css">
<title>银河锋锐(GVSDS)</title>
<link rel="icon" href="https://hb.cdn.gvsds.com/assets/icon/ICON.png">
<script type="module" crossorigin src="/GVSite/index-tyrWQVZX.js"></script>
<link rel="stylesheet" crossorigin href="/GVSite/index-BY1hqLY-.css">
</head>
<body>
<div id="app"></div>
+8 -2
View File
@@ -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)
+1 -2
View File
@@ -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',
+42
View File
@@ -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
+2
View File
@@ -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