适配新后端认证 修复了身份过期导致无限重定向的bug

This commit is contained in:
root 2024-12-02 23:42:33 +08:00
parent 32fe02787c
commit e84f0499db
11 changed files with 151 additions and 13 deletions

View file

@ -1 +1 @@
VITE_API_BASE_URL=http://localhost:5098 VITE_API_BASE_URL=http://admin.gitdl.cn:50050

14
package-lock.json generated
View file

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.1.0",
"@fingerprintjs/fingerprintjs": "^4.5.1",
"axios": "^1.7.8", "axios": "^1.7.8",
"element-plus": "^2.9.0", "element-plus": "^2.9.0",
"vue": "^3.3.4", "vue": "^3.3.4",
@ -429,6 +430,14 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@fingerprintjs/fingerprintjs": {
"version": "4.5.1",
"resolved": "https://registry.npmmirror.com/@fingerprintjs/fingerprintjs/-/fingerprintjs-4.5.1.tgz",
"integrity": "sha512-hKJaRoLHNeUUPhb+Md3pTlY/Js2YR4aXjroaDHpxrjoM8kGnEFyZVZxXo6l3gRyKnQN52Uoqsycd3M73eCdMzw==",
"dependencies": {
"tslib": "^2.4.1"
}
},
"node_modules/@floating-ui/core": { "node_modules/@floating-ui/core": {
"version": "1.6.8", "version": "1.6.8",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.8.tgz", "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.8.tgz",
@ -989,6 +998,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/vite": { "node_modules/vite": {
"version": "4.5.5", "version": "4.5.5",
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.5.5.tgz", "resolved": "https://registry.npmmirror.com/vite/-/vite-4.5.5.tgz",

View file

@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.1.0",
"@fingerprintjs/fingerprintjs": "^4.5.1",
"axios": "^1.7.8", "axios": "^1.7.8",
"element-plus": "^2.9.0", "element-plus": "^2.9.0",
"vue": "^3.3.4", "vue": "^3.3.4",

View file

@ -20,8 +20,13 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { UserAPI } from '@/api/user'
import { initFingerprint } from '@/utils/fingerprint'
const router = useRouter()
const isLoading = ref(false) const isLoading = ref(false)
const showLoadingMask = () => { const showLoadingMask = () => {
@ -31,6 +36,50 @@ const showLoadingMask = () => {
const hideLoadingMask = () => { const hideLoadingMask = () => {
isLoading.value = false isLoading.value = false
} }
//
const checkAuthStatus = async () => {
const token = localStorage.getItem('token')
const isAuthenticated = localStorage.getItem('isAuthenticated')
// token
if (token && isAuthenticated) {
try {
const response = await UserAPI.getUserInfo()
if (response.retcode !== 0) {
clearAuthData('登录已失效,请重新登录')
}
} catch (error) {
// 401
if (error.response?.status === 401) {
clearAuthData('登录已过期,请重新登录')
}
}
}
}
//
const clearAuthData = (message) => {
localStorage.removeItem('token')
localStorage.removeItem('isAuthenticated')
localStorage.removeItem('userRole')
localStorage.removeItem('userInfo')
// 访
if (!router.currentRoute.value.path.startsWith('/visitor')) {
if (message) {
ElMessage.warning(message)
}
router.push('/login')
}
}
onMounted(async () => {
//
await initFingerprint()
//
await checkAuthStatus()
})
</script> </script>
<style> <style>

View file

@ -1,8 +1,11 @@
import axios from 'axios' import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage } from 'element-plus'
import router from '../router' import router from '../router'
import { API_BASE_URL } from '../config/api.config' import { API_BASE_URL } from '../config/api.config'
// 创建一个全局的指纹变量
let globalFingerprint = null
const request = axios.create({ const request = axios.create({
baseURL: API_BASE_URL, baseURL: API_BASE_URL,
timeout: 10000, timeout: 10000,
@ -14,6 +17,11 @@ const request = axios.create({
// 请求拦截器 // 请求拦截器
request.interceptors.request.use( request.interceptors.request.use(
config => { config => {
// 如果有全局指纹,直接使用
if (globalFingerprint) {
config.headers['X-Device-Fingerprint'] = globalFingerprint
}
// 如果是访客页面的请求,不需要添加 token // 如果是访客页面的请求,不需要添加 token
if (!config.url.startsWith('/visitor')) { if (!config.url.startsWith('/visitor')) {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
@ -35,6 +43,14 @@ request.interceptors.response.use(
const { data } = response const { data } = response
console.log('Response Data:', data) console.log('Response Data:', data)
// 登录接口特殊处理
if (response.config.url === '/user/login') {
if (data.retcode === 0) {
localStorage.setItem('isAuthenticated', 'true')
}
return data
}
if (data.retcode === 0) { if (data.retcode === 0) {
return data return data
} }
@ -49,10 +65,14 @@ request.interceptors.response.use(
if (error.response) { if (error.response) {
switch (error.response.status) { switch (error.response.status) {
case 401: case 401:
// 只有非访客页面才需要清除登录状态并跳转 // 清除所有认证相关的存储
localStorage.removeItem('token')
localStorage.removeItem('isAuthenticated')
localStorage.removeItem('userRole')
localStorage.removeItem('userInfo')
// 只有非访客页面才需要跳转
if (!router.currentRoute.value.path.startsWith('/visitor')) { if (!router.currentRoute.value.path.startsWith('/visitor')) {
localStorage.removeItem('token')
localStorage.removeItem('userRole')
router.push('/login') router.push('/login')
} }
break break
@ -71,4 +91,9 @@ request.interceptors.response.use(
} }
) )
// 导出一个设置指纹的方法
export const setGlobalFingerprint = (fingerprint) => {
globalFingerprint = fingerprint
}
export default request export default request

View file

@ -1,4 +1,4 @@
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000' export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://admin.gitdl.cn:50050'
export const API_ENDPOINTS = { export const API_ENDPOINTS = {
TASK: { TASK: {
@ -7,4 +7,4 @@ export const API_ENDPOINTS = {
} }
} }
export const ADMIN_ROUTE_BASE = "/admin" export const ADMIN_ROUTE_BASE = "/admin"

View file

@ -5,6 +5,8 @@ import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue' import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { initFingerprint } from '@/utils/fingerprint'
import { setGlobalFingerprint } from '@/api/request'
const app = createApp(App) const app = createApp(App)
@ -19,4 +21,14 @@ app.use(ElementPlus, {
}) })
app.use(router) app.use(router)
app.mount('#app')
// 初始化指纹后再挂载应用
initFingerprint().then(fingerprint => {
if (fingerprint) {
setGlobalFingerprint(fingerprint)
}
app.mount('#app')
}).catch(() => {
// 即使获取指纹失败也继续挂载应用
app.mount('#app')
})

Binary file not shown.

29
src/utils/fingerprint.js Normal file
View file

@ -0,0 +1,29 @@
import FingerprintJS from '@fingerprintjs/fingerprintjs'
let cachedFingerprint = null
let fpPromise = null
// 初始化函数
export async function initFingerprint() {
if (!fpPromise) {
fpPromise = FingerprintJS.load()
}
try {
const fp = await fpPromise
const result = await fp.get()
cachedFingerprint = result.visitorId
return cachedFingerprint
} catch (error) {
console.error('初始化浏览器指纹失败:', error)
return null
}
}
export async function getFingerprint() {
if (cachedFingerprint) {
return cachedFingerprint
}
return initFingerprint()
}

View file

@ -280,7 +280,7 @@ const dateShortcuts = [
// //
const logLevels = { const logLevels = {
'Verbose': { text: '详细', type: '' }, 'Verbose': { text: '详细', type: 'info' },
'Debug': { text: '调试', type: 'info' }, 'Debug': { text: '调试', type: 'info' },
'Information': { text: '信息', type: 'success' }, 'Information': { text: '信息', type: 'success' },
'Warning': { text: '警告', type: 'warning' }, 'Warning': { text: '警告', type: 'warning' },

View file

@ -95,13 +95,12 @@ const handleLogin = async () => {
}) })
if (response.retcode === 0) { if (response.retcode === 0) {
localStorage.setItem('token', JSON.stringify(response.data)) localStorage.setItem('token', JSON.stringify(response.data))
localStorage.setItem('isAuthenticated', 'true')
localStorage.setItem('userRole', 'admin')
// //
const userInfoResponse = await UserAPI.getUserInfo() const userInfoResponse = await UserAPI.getUserInfo()
if (userInfoResponse.retcode === 0) { if (userInfoResponse.retcode === 0) {
localStorage.setItem('userInfo', JSON.stringify(userInfoResponse.data)) localStorage.setItem('userInfo', JSON.stringify(userInfoResponse.data))
localStorage.setItem('userRole', 'admin') //
// //
if (userInfoResponse.data.mask >= UserMask.Admin) { if (userInfoResponse.data.mask >= UserMask.Admin) {
@ -111,12 +110,21 @@ const handleLogin = async () => {
} }
ElMessage.success('登录成功') ElMessage.success('登录成功')
} else {
// token
localStorage.removeItem('token')
ElMessage.error('获取用户信息失败')
} }
} else { } else {
ElMessage.error(response.message || '登录失败') ElMessage.error(response.message || '登录失败')
} }
} catch (error) { } catch (error) {
console.error('登录失败:', error) console.error('登录失败:', error)
//
localStorage.removeItem('token')
localStorage.removeItem('isAuthenticated')
localStorage.removeItem('userRole')
localStorage.removeItem('userInfo')
ElMessage.error('登录失败,请重试') ElMessage.error('登录失败,请重试')
} finally { } finally {
loading.value = false loading.value = false