修复链上的 8 个真 bug: 1. UserDetail(UserVO,Set) 漏 setAuthorities → CAS token 构造抛 IllegalArgumentException 修:CasUserDetailsService.buildGptUserDetail 手动补 empty authorities 2. frontEndUrl 写死内网 IP,localhost 隧道用户跳回时"无法访问此网站" 修:CasUrlBuilder 用 X-Forwarded-Host / Host 动态拼 service URL 3. vite proxy 没配 /metalinfo/chat_web_backend,CAS 回跳 ticket 被前端路由吞 修:加一条 proxy(rewrite 去 /metalinfo 前缀)+ X-Forwarded-Host 转发 4. ticket 校验 service URL 跟 entry point 不一致 → CAS server mismatch 修:自定义 AuthenticationDetailsSource 用同一个动态 URL 5. sendRedirect URL 含 # 经容器编码成 %23,浏览器拿不到 hash → 404 修:改用 query 参数(/metalinfo/?cas_token=xxx),前端 router beforeEach 拦截 6. CAS 登录后 HttpSession 残留,第二次访问 /cas/login 不触发 entryPoint → 落到 DispatcherServlet → 找不到映射 → 404 Whitelabel 修:SuccessHandler 完成后 invalidate session + clear SecurityContext 7. CAS 路径漏写 Redis token,JwtAuthenticationFilter 校验时 LOGIN_TOKEN_KEY 找不到 → "token已失效" → 前端 axios interceptor 清 token 跳回 login 修:SuccessHandler 同步写 redisUtils.set,与 LoginController.saveLoginLog 对齐 8. permission.ts 没拦 query 里的 cas_token,hash 路由下 location.search 取不到 修:router beforeEach 优先消费 cas_token 再走 getSession 清理冗余: - CasProperties 删 6 个未用字段(enabled/serverLogout/appLogout/appKey/ appSecret/httpsFlag/frontEndUrl) - application.yml 同步删,移除写死的 app-secret 等敏感字段 - 删外部 override 文件 chat_web_backend/config/application.yml - casServiceProperties.setService 改占位符(实际不被读取,只满足 ServiceProperties.afterPropertiesSet 的非空校验) - 删 permission.ts 的 [CAS] [GUARD] debug log,保留 catch error 一条 新增: - CasUrlBuilder 工具类:从请求动态解析 host/scheme,多个地方共用 UI: - welcome 页面玻璃按钮 + 呼吸光晕/光感动画(用户自己调过,本次保留) - App.vue:/welcome 路径不渲染 Operates 侧边栏 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
80 lines
2.6 KiB
TypeScript
80 lines
2.6 KiB
TypeScript
import { fileURLToPath, URL } from 'node:url'
|
||
|
||
import { defineConfig,loadEnv } from 'vite'
|
||
import type { PluginOption } from 'vite'
|
||
import vue from '@vitejs/plugin-vue'
|
||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||
import AutoImport from 'unplugin-auto-import/vite'
|
||
import Components from 'unplugin-vue-components/vite'
|
||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||
|
||
function setupPlugins(env: ImportMetaEnv): PluginOption[] {
|
||
return [
|
||
vue(),
|
||
vueDevTools(),
|
||
// topLevelAwait(),
|
||
AutoImport({
|
||
resolvers: [ElementPlusResolver()],
|
||
}),
|
||
Components({
|
||
resolvers: [ElementPlusResolver()],
|
||
}),
|
||
]
|
||
}
|
||
|
||
export default defineConfig((env) => {
|
||
const viteEnv = loadEnv(env.mode, process.cwd()) as unknown as ImportMetaEnv
|
||
return {
|
||
base: viteEnv.VITE_GLOB_FRONT_CTX,
|
||
plugins: setupPlugins(viteEnv),
|
||
server: {
|
||
host: '0.0.0.0',
|
||
port: viteEnv.VITE_GLOB_FRONT_PORT,
|
||
open: false,
|
||
proxy: {
|
||
[viteEnv.VITE_GLOB_API_CTX]: {
|
||
target: viteEnv.VITE_GLOB_API_DEV_IP,
|
||
changeOrigin: true,
|
||
// 转发原 Host,让后端能用 X-Forwarded-Host 拼出用户浏览器的实际 URL
|
||
configure: (proxy) => {
|
||
proxy.on('proxyReq', (proxyReq, req) => {
|
||
if (req.headers.host) proxyReq.setHeader('X-Forwarded-Host', req.headers.host)
|
||
proxyReq.setHeader('X-Forwarded-Proto', 'http')
|
||
})
|
||
},
|
||
},
|
||
// CAS 回调:service URL 形如 /metalinfo/chat_web_backend/cas/login?ticket=xxx
|
||
// 把 /metalinfo 前缀去掉转给后端,否则 vite 当前端路由吃掉
|
||
[`${viteEnv.VITE_GLOB_FRONT_CTX}${viteEnv.VITE_GLOB_API_CTX}`]: {
|
||
target: viteEnv.VITE_GLOB_API_DEV_IP,
|
||
changeOrigin: true,
|
||
rewrite: (p) => p.replace(new RegExp(`^${viteEnv.VITE_GLOB_FRONT_CTX}`), ''),
|
||
configure: (proxy) => {
|
||
proxy.on('proxyReq', (proxyReq, req) => {
|
||
if (req.headers.host) proxyReq.setHeader('X-Forwarded-Host', req.headers.host)
|
||
proxyReq.setHeader('X-Forwarded-Proto', 'http')
|
||
})
|
||
},
|
||
},
|
||
// 工具服务通过 Nginx(:18000) 反代,sub_filter 处理子资源路径
|
||
'/pdf/': {
|
||
target: 'http://localhost:18000',
|
||
changeOrigin: true,
|
||
},
|
||
'/draw/': {
|
||
target: 'http://localhost:18000',
|
||
changeOrigin: true,
|
||
},
|
||
},
|
||
},
|
||
build: {
|
||
target: 'esnext',
|
||
},
|
||
resolve: {
|
||
alias: {
|
||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||
},
|
||
},
|
||
}
|
||
})
|