feat: 重构鉴权系统为双Token无感刷新,并增加前端Token过期安全预判机制

This commit is contained in:
DXC
2026-03-10 09:45:41 +08:00
parent 6fc6851e57
commit e4632086a1
6 changed files with 321 additions and 35 deletions

View File

@ -2,14 +2,47 @@ import { defineStore } from 'pinia'
import { login } from '@/api/auth'
import { getRolePermissions } from '@/api/system/permission'
import { ref } from 'vue'
import axios from 'axios'
// JWT 解码函数(手写实现,不依赖外部库)
function parseJWT(token: string): any {
if (!token) return null
try {
const base64Url = token.split('.')[1]
if (!base64Url) return null
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
const jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
)
return JSON.parse(jsonPayload)
} catch (e) {
console.error('JWT parse error:', e)
return null
}
}
// 获取 Token 过期时间戳
function getTokenExp(token: string): number | null {
const decoded = parseJWT(token)
return decoded?.exp ? decoded.exp * 1000 : null // 转换为毫秒
}
export const useUserStore = defineStore('user', () => {
// 1. State: 初始化时优先从 localStorage 获取,防止刷新丢失
const token = ref(localStorage.getItem('token') || '')
const token = ref(localStorage.getItem('access_token') || localStorage.getItem('token') || '')
const refreshToken = ref(localStorage.getItem('refresh_token') || '')
const role = ref(localStorage.getItem('role') || '')
const username = ref(localStorage.getItem('username') || '')
const permissions = ref<string[]>(JSON.parse(localStorage.getItem('permissions') || '[]'))
// 兼容旧版 localStorage key
if (localStorage.getItem('token') && !localStorage.getItem('access_token')) {
localStorage.setItem('access_token', localStorage.getItem('token') || '')
}
// 2. Actions
// 登录逻辑
const handleLogin = async (loginForm: any) => {
@ -32,6 +65,7 @@ export const useUserStore = defineStore('user', () => {
// 更新 Pinia 状态 (内存)
token.value = data.access_token
refreshToken.value = data.refresh_token || ''
// 处理用户信息 (确保后端返回结构中有 user 字段)
if (data.user) {
@ -44,8 +78,11 @@ export const useUserStore = defineStore('user', () => {
localStorage.setItem('username', username.value)
}
// 持久化存储 Token
localStorage.setItem('token', data.access_token)
// 持久化存储 Token
localStorage.setItem('access_token', data.access_token)
if (data.refresh_token) {
localStorage.setItem('refresh_token', data.refresh_token)
}
// 登录成功后,根据角色获取权限
if (role.value) {
@ -69,15 +106,24 @@ export const useUserStore = defineStore('user', () => {
return true // 返回 true 表示登录成功
}
// 更新 Access Token无感刷新时调用
const setToken = (newToken: string) => {
token.value = newToken
localStorage.setItem('access_token', newToken)
}
// 退出逻辑
const logout = () => {
// 1. 清空 Pinia 状态 (内存)
token.value = ''
refreshToken.value = ''
role.value = ''
username.value = ''
permissions.value = []
// 2. 清空 LocalStorage (硬盘)
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
localStorage.removeItem('token')
localStorage.removeItem('role')
localStorage.removeItem('username')
@ -107,6 +153,16 @@ export const useUserStore = defineStore('user', () => {
}
}
// 检查 refresh_token 是否即将过期30分钟
const isRefreshTokenExpiringSoon = (): boolean => {
if (!refreshToken.value) return true
const expTime = getTokenExp(refreshToken.value)
if (!expTime) return true
const now = Date.now()
const thirtyMinutes = 30 * 60 * 1000
return expTime - now < thirtyMinutes
}
// 3. Getters / Helpers
// 判断当前用户是否拥有某些角色
const hasRole = (roles: string[]) => {
@ -124,12 +180,15 @@ export const useUserStore = defineStore('user', () => {
return {
token,
refreshToken,
role,
username,
permissions,
handleLogin,
setToken,
logout,
refreshUserPermissions,
isRefreshTokenExpiringSoon,
hasRole,
hasPermission
}