feat: 重构鉴权系统为双Token无感刷新,并增加前端Token过期安全预判机制
This commit is contained in:
@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user