用户登录系统后,都会赋予用户一个Token,这样用户在操作系统时,只需要将Token传给后端就可以了,无需对用户重复进行身份验证
既然Token如此重要,为了安全起见,Token的有效期也不会设置过长。
那么将由前后端谁来判断Token是否失效呢?只能说各有优劣
// 简单说一下主要几点
// 前端
优点:体验好,减少服务端的负担;
缺点:不安全,依赖本地时间,可以被伪造或篡改,容易被攻击者利用;
// 后端
优点:安全性高,只有服务端知道Token是否失效,可以防止客户端伪造或篡改信息;还能简化前端处理逻辑;
缺点:增加了服务端的负担,每次请求都需要进行验证,可能对性能有影响
本期主要重点讲 前端是如何处理Token失效逻辑的。
为了不频繁更新Token,前端应该设置一个更新时间和失效时间
场景和逻辑很简单:
1、用户登录系统,后端返回前端Token和 有效期时间(3天);
那么更新时间设为2天半后,失效时间设为3天后
2、用户在3天后访问系统判断其Token失效,去往登录页重新登录
3、用户在2天半前访问系统,则不需要特殊处理,使用本地储存的Token就可以了;
更新时间不变,但是失效时间应该改为 当前时间 + 3天;
4、用户在2天半 — 3天这个时间段访问系统,则需要请求后端返回新Token;
更新时间和失效时间 则需要执行步骤1的逻辑
主要有两处地方需要添加逻辑:
1、项目中 pinia/vuex 状态管理库,需要对时间进行储存和判断
2、接口请求时,公共api层是否需要更新token
// 不同项目,不同开发习惯,目录结构都不一样; 这里主要凸显出这是一个状态管理库文件
// store/modules/global.js
// 使用pinia储存: 更新时间、失效时间、Token
import { defineStore } from 'pinia'
export const glStore = defineStore('glStore', {
persist: {
storage: {
setItem(key, value) {
uni.setStorageSync(key, value)
},
getItem(key) {
return uni.getStorageSync(key)
}
}
},
state: () => {
return {
token: '',
userInfo: {},
// 失效时间
expirationTime: 0,
// 更新时间
updateTime: 0
}
},
actions: {
setToken(value) {
this.token = value
},
setUserInfo(userInfo) {
if (userInfo.token) {
this.setToken(userInfo.token)
delete userInfo.token
}
this.userInfo = userInfo
},
getDate() {
// 3天
const threshold = 3 * 24 * 60 * 60 * 1000
// 失效前半天
const interval = 12 * 60 * 60 * 1000
const now = new Date()
// 当前时间
const currentTime = now.getTime()
// 失效时间
const failure = currentTime + threshold
// 更新时间
const update = threshold + currentTime - interval
console.log(update, this.formatTime(update), '--------2天半后')
console.log(failure, this.formatTime(failure), '--------3天后')
console.log(currentTime, this.formatTime(currentTime), '--------当前时间')
// 1:更新Token;
// 2:更新失效期;
// 3:Token失效了;
// 4:未知
if (!this.expirationTime) {
this.expirationTime = failure
this.updateTime = update
return '1'
} else {
// 当前时间 小于 更新时间
if (currentTime < this.updateTime) {
// 更新失效时间为 最新当前时间的3天后
this.expirationTime = failure
return '2'
} else if (currentTime > this.updateTime && currentTime > this.expirationTime) {
// 更新Token--- 重复最开始操作
this.expirationTime = failure
this.updateTime = update
return '1'
} else if (currentTime > this.updateTime) {
// 当前时间 大于 失效时间 == Token失效
return '3'
} else {
return '4'
}
}
},
formatTime(timestamp) {
const date = new Date(timestamp)
const formattedDate = date.toLocaleString()
return formattedDate
}
}
})
// 接口域名
import { host } from '$api/baseUrl.js'
// 设置请求头
import { setHeader } from './sign'
// pinia
import { glStore } from '@/store/modules/global.js'
// 请求后拦截
function responseFn(res, resolve, reject) {
if (res.code === 0) {
resolve(res)
} else if (res.code === 401300) {
// token过期
uni.showModal({
// 弹出提示框
title: '重新登录',
content: '登录过期,请重新登录!',
success(res) {
if (res.confirm) {
// 用户点击确定按钮
uni.navigateTo({ url: '/pages/login/index' })
}
},
fail(e) {
console.log(e, '确认取消弹出未弹出')
}
})
} else {
uni.showToast({
title: res.msg,
icon: 'none',
duration: 1000
})
reject(res)
}
}
async function fetch(url, data = {}, method = 'POST') {
const store = glStore()
let type = store.getDate()
// 不需要Token的接口
let urls = ['xxx/code', 'xxx/login']
if (type === '1' && !urls.includes(url)) {
let res = await $ajax('url---token', data, method)
store.setUserInfo(res.data)
}
return $ajax(url, data, method)
}
async function $ajax(url, data = {}, method = 'POST') {
return new Promise((resolve, reject) => {
uni.request({
url: host + url,
header: setHeader({
url,
method,
data: data || {}
}),
data,
method,
success(res) {
responseFn(res.data, resolve, reject)
},
fail: function(err) {
uni.hideLoading()
reject(err)
}
})
})
}
const $get = (url = '', data = {}) => {
return fetch(
url,
data,
'get'
)
}
const $post = (url = '', data = {}) => {
return fetch(
url,
data
)
}
const $delete = (url = '', data = {}) => {
return fetch(
url,
data,
'delete'
)
}
const $put = (url = '', data = {}) => {
return fetch(
url,
data,
'put'
)
}
export { $get, $post, $delete, $put }