From 6ffdbcb3d77340c77de88a402637ecb99fa549cd Mon Sep 17 00:00:00 2001 From: Kroytz Date: Sun, 19 Jul 2026 00:32:14 +0800 Subject: [PATCH] fix(router): validate server session before loading routes --- yshop-drink-vue3/src/config/axios/service.ts | 5 +- yshop-drink-vue3/src/permission.ts | 71 ++++++++++--------- yshop-drink-vue3/src/store/modules/user.ts | 6 -- yshop-drink-vue3/src/utils/auth.ts | 6 -- .../src/views/Login/SocialLogin.vue | 2 - .../src/views/Login/components/LoginForm.vue | 1 - .../src/views/Login/components/MobileForm.vue | 3 +- 7 files changed, 40 insertions(+), 54 deletions(-) diff --git a/yshop-drink-vue3/src/config/axios/service.ts b/yshop-drink-vue3/src/config/axios/service.ts index 163f184..b3292da 100644 --- a/yshop-drink-vue3/src/config/axios/service.ts +++ b/yshop-drink-vue3/src/config/axios/service.ts @@ -9,7 +9,7 @@ import axios, { import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' import qs from 'qs' import { config } from '@/config/axios/config' -import { getTenantId, removeAuthenticated } from '@/utils/auth' +import { getTenantId } from '@/utils/auth' import errorCode from './errorCode' import { resetRouter } from '@/router' @@ -201,9 +201,8 @@ const handleAuthorized = () => { type: 'warning' }).then(() => { resetRouter() // 重置静态路由表 - removeAuthenticated() isRelogin.show = false - // 干掉token后再走一次路由让它过router.beforeEach的校验 + // 重新加载后由路由守卫向服务端确认会话 window.location.href = window.location.href }) } diff --git a/yshop-drink-vue3/src/permission.ts b/yshop-drink-vue3/src/permission.ts index 79fd102..e9b141b 100644 --- a/yshop-drink-vue3/src/permission.ts +++ b/yshop-drink-vue3/src/permission.ts @@ -1,7 +1,6 @@ -import router from './router' +import router, { resetRouter } from './router' import type { RouteRecordRaw } from 'vue-router' import { isRelogin } from '@/config/axios/service' -import { isAuthenticated } from '@/utils/auth' import { useTitle } from '@/hooks/web/useTitle' import { useNProgress } from '@/hooks/web/useNProgress' import { usePageLoading } from '@/hooks/web/usePageLoading' @@ -60,42 +59,46 @@ const whiteList = [ router.beforeEach(async (to, from, next) => { start() loadStart() - if (isAuthenticated()) { - if (to.path === '/login') { + const userStore = useUserStoreWithOut() + if (whiteList.includes(to.path)) { + if (to.path === '/login' && userStore.getIsSetUser) { next({ path: '/' }) } else { - // 获取所有字典 - const dictStore = useDictStoreWithOut() - const userStore = useUserStoreWithOut() - const permissionStore = usePermissionStoreWithOut() - if (!dictStore.getIsSetDict) { - await dictStore.setDictMap() - } - if (!userStore.getIsSetUser) { - isRelogin.show = true - await userStore.setUserInfoAction() - isRelogin.show = false - // 后端过滤菜单 - await permissionStore.generateRoutes(userStore.getMenus) - permissionStore.getAddRouters.forEach((route) => { - router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表 - }) - const redirectPath = from.query.redirect || to.path - // 修复跳转时不带参数的问题 - const redirect = decodeURIComponent(redirectPath as string) - const { basePath, paramsObject: query } = parseURL(redirect) - const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect, query } - next(nextData) - } else { - next() - } - } - } else { - if (whiteList.indexOf(to.path) !== -1) { next() - } else { - next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 } + return + } + + if (userStore.getIsSetUser) { + next() + return + } + + isRelogin.show = true + try { + await userStore.setUserInfoAction() + + const dictStore = useDictStoreWithOut() + if (!dictStore.getIsSetDict) { + await dictStore.setDictMap() + } + + const permissionStore = usePermissionStoreWithOut() + await permissionStore.generateRoutes(userStore.getMenus) + permissionStore.getAddRouters.forEach((route) => { + router.addRoute(route as unknown as RouteRecordRaw) + }) + + const redirectPath = from.query.redirect || to.path + const redirect = decodeURIComponent(redirectPath as string) + const { paramsObject: query } = parseURL(redirect) + next(to.path === redirect ? { ...to, replace: true } : { path: redirect, query }) + } catch { + userStore.resetState() + resetRouter() + next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) + } finally { + isRelogin.show = false } }) diff --git a/yshop-drink-vue3/src/store/modules/user.ts b/yshop-drink-vue3/src/store/modules/user.ts index 3770827..b9b064f 100644 --- a/yshop-drink-vue3/src/store/modules/user.ts +++ b/yshop-drink-vue3/src/store/modules/user.ts @@ -1,6 +1,5 @@ import { store } from '@/store' import { defineStore } from 'pinia' -import { isAuthenticated, removeAuthenticated } from '@/utils/auth' import { getInfo, loginOut } from '@/api/login' interface UserVO { @@ -51,10 +50,6 @@ export const useUserStore = defineStore('admin-user', { }, actions: { async setUserInfoAction() { - if (!isAuthenticated()) { - this.resetState() - return null - } const userInfo = await getInfo() this.permissions = userInfo.permissions this.roles = userInfo.roles @@ -72,7 +67,6 @@ export const useUserStore = defineStore('admin-user', { try { await loginOut() } finally { - removeAuthenticated() this.resetState() } }, diff --git a/yshop-drink-vue3/src/utils/auth.ts b/yshop-drink-vue3/src/utils/auth.ts index 2bb8f14..5bde65d 100644 --- a/yshop-drink-vue3/src/utils/auth.ts +++ b/yshop-drink-vue3/src/utils/auth.ts @@ -1,18 +1,12 @@ import { useCache, CACHE_KEY } from '@/hooks/web/useCache' const { wsCache } = useCache() -const { wsCache: sessionCache } = useCache('sessionStorage') wsCache.delete('ACCESS_TOKEN') wsCache.delete('REFRESH_TOKEN') wsCache.delete('user') wsCache.delete('roleRouters') -const AuthenticatedKey = 'AUTHENTICATED' - -export const isAuthenticated = () => sessionCache.get(AuthenticatedKey) === true -export const setAuthenticated = () => sessionCache.set(AuthenticatedKey, true) -export const removeAuthenticated = () => sessionCache.delete(AuthenticatedKey) // ========== 账号相关 ========== export type LoginFormType = { diff --git a/yshop-drink-vue3/src/views/Login/SocialLogin.vue b/yshop-drink-vue3/src/views/Login/SocialLogin.vue index c581239..6361f6c 100644 --- a/yshop-drink-vue3/src/views/Login/SocialLogin.vue +++ b/yshop-drink-vue3/src/views/Login/SocialLogin.vue @@ -252,7 +252,6 @@ const tryLogin = async () => { const state = route?.query?.state as string await LoginApi.socialLogin(type, code, state) - authUtil.setAuthenticated() router.push({ path: redirect || '/' }) } catch (err) {} @@ -297,7 +296,6 @@ const handleLogin = async (params) => { } else { authUtil.removeLoginForm() } - authUtil.setAuthenticated() if (!redirect) { redirect = '/' } diff --git a/yshop-drink-vue3/src/views/Login/components/LoginForm.vue b/yshop-drink-vue3/src/views/Login/components/LoginForm.vue index 483e3e2..5fd4061 100644 --- a/yshop-drink-vue3/src/views/Login/components/LoginForm.vue +++ b/yshop-drink-vue3/src/views/Login/components/LoginForm.vue @@ -186,7 +186,6 @@ const handleLogin = async (params) => { } else { authUtil.removeLoginForm() } - authUtil.setAuthenticated() if (!redirect.value) { redirect.value = '/' } diff --git a/yshop-drink-vue3/src/views/Login/components/MobileForm.vue b/yshop-drink-vue3/src/views/Login/components/MobileForm.vue index 01489c2..e8fb83b 100644 --- a/yshop-drink-vue3/src/views/Login/components/MobileForm.vue +++ b/yshop-drink-vue3/src/views/Login/components/MobileForm.vue @@ -97,7 +97,7 @@ import type { RouteLocationNormalizedLoaded } from 'vue-router' import { useIcon } from '@/hooks/web/useIcon' -import { setTenantId, setAuthenticated } from '@/utils/auth' +import { setTenantId } from '@/utils/auth' import { usePermissionStore } from '@/store/modules/permission' import { getTenantIdByName, sendSmsCode, smsLogin } from '@/api/login' import LoginFormTitle from './LoginFormTitle.vue' @@ -196,7 +196,6 @@ const signIn = async () => { smsVO.loginSms.code = loginData.loginForm.code await smsLogin(smsVO.loginSms) .then(async () => { - setAuthenticated() if (!redirect.value) { redirect.value = '/' }