赞
踩
本文实例代码使用的是vue+axiosÏ
Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
请求后台返回的登录数据一般情况如下
{
access_token:"加密的字符串",
expires_in:"7200",
refresh_token:"加密的字符串",
}
因为业务模式多种多样所以使用方法也是有很多的(ps:主要是后端想要什么,我们就给什么),比较常见的是Header中携带Token。
/**
* 用户请求模块
*/
export const login = (dataÏ) => request({
url: '/front/user/login',
method: 'POST',
data: qs.stringify(data)
})
export default new Vuex.Store({
state: {
// 初始化
user: null
},
mutations: {
// 设置用户登录信息
setUser(state, payload) {
//因目前后端返回的是json字符串,所以我转义了一下
payload = JSON.parse(payload)
//如果pyload中没有过期时间并且存在过期时间长度
if (!payload.expires_at && payload.expires_in) {
//设置过期时间
payload.expires_at = new Date().getTime() + payload.expires_in * 1000
}
//赋值
state.user = payload
}
},
actions: {
},
modules: {
}
})
import Vue from 'vue'
import { login } from '@/services/user'
export default Vue.extend({
name: 'LoginIndex',
data() {
return {
formData: {
phone: '18201288771',
password: '111111'
}
}
},
methods: {
async submit() {
try {
const { data } = await login(this.formData)
// 处理请求结果
if (data.state !== 1) {
this.$message.error(data.message)
} else {
// 使用vuex中的setUser共享登录信息
this.$store.commit('setUser', data.content)
this.$message.success('登录成功')
}
} catch (error) {}
this.isLoading = false
}
}
})
// 请求拦截器,每一个请求都会经过此拦截器。
request.interceptors.request.use((config) => {
// 在请求的header中设置token
config.headers.Authorization = store.state?.user?.access_token
return config
}, (error) => {
return Promise.reject(error)
})
// 跳转至首页封装
const redirectLogin = () => {
router.push({
name: 'login',
query: {
// 通过参数传 登录成功后的跳转地址
redirect: router.currentRoute.fullPath
}
})
}
methods: {
// 登录请求方法
async submit() {
try {
const { data } = await login(this.formData)
// 处理请求结果
if (data.state !== 1) {
//..... 登录失败处理逻辑
} else {
//..... 登录成功处理逻辑
// 登录成功后进行路由跳转
this.$router.push((this.$route.query.redirect as string) || '/')
}
} catch (error) {}
}
}
export default new Vuex.Store({
state: {
// 初始化时从本地存储中获取
user: JSON.parse(window.localStorage.getItem('user') || 'null')
},
mutations: {
//设置用户登录信息
setUser(state, payload) {
//因目前后端返回的是json字符串,所以我转义了一下
payload = JSON.parse(payload)
//如果pyload中没有过期时间并且存在过期时间长度
if (!payload.expires_at && payload.expires_in) {
//设置过期时间
payload.expires_at = new Date().getTime() + payload.expires_in * 1000
}
//赋值
state.user = payload
//每次设置用户登录信息都存储值本地存储
window.localStorage.setItem('user', JSON.stringify(payload))
}
},
actions: {
},
modules: {
}
})
过期维护存前端存在两种方式
// 跳转首页逻辑
const redirectLogin = () => {
router.push({
name: 'login',
query: {
redirect: router.currentRoute.fullPath
}
})
}
// 刷新token后的任务队列
let refreshTokenArray = []
/**
* 刷新token,重新请求
*/
const refreshTokenFn = async () => {
// 判断是否有刷新token
const refreshToken = store.state?.user?.refresh_token || ''
// 如果刷新token存在
if (refreshToken) {
// 使用重新创建的axios请求,防止递归调用
const { data } = await axios.create()({
method: 'POST',
url: '/front/user/refresh_token',
data: qs.stringify({
refreshtoken: refreshToken
})
})
//如果获取token失败 抛出异常
if (!data.content) throw new Error('refreshToken is faild')
// 重新设置token
store.commit('setUser', data.content)
return true
}
throw new Error('refreshToken not find')
}
// 请求拦截器
request.interceptors.request.use(async (config: Config) => {
// 获取用户登录信息
const user = store.state?.user
// 判断access_token 是否过期且接口是否需要token
if (config.isAuthToken && user.expires_at < new Date().getTime()) {
// 是否正在执行刷新token
if (!refreshTokenLoding) {
try {
//刷新token锁为true
refreshTokenLoding = true
await refreshTokenFn()
// 执行获取token后的任务队列
refreshTokenArray.forEach(item => item())
//清空任务队列
refreshTokenArray = []
return config
} catch (error) {
// 如果刷新失败跳转登录页面
redirectLogin()
} finally {
// 无论成功失败消除
refreshTokenLoding = false
}
} else {
// 如果这正在刷新,返回一个 Promise ,并向刷新token成功后执行队列push 函数.
return new Promise(resolve => {
refreshTokenArray.push(() => {
// 返回config请求对象
resolve(config)
})
})
}
}
return config
})
//相应拦截器
request.interceptors.response.use((response) => {
// 2xx 会进入这里
return response
}, async (error) => {
// 判断是否是授权错误
if (error.response === 401) {
// 是否正在刷新
if (!refreshTokenLoding) {
refreshTokenLoding = true
// 尝试使用 refresh_token 获取新的 access_token
try {
// 执行刷新token
await refreshTokenFn()
// 执行刷新后任务队列
refreshTokenArray.forEach(item => item())
//清除任务队列
refreshTokenArray = []
// 重发当前请求
return request(error.config)
// 如果成功 则重发上次请求
} catch (error) {
// 如果失败 跳转至登录
redirectLogin()
} finally {
refreshTokenLoding = false
}
} else {
// 如果当前正在请求
return new Promise(resolve => {
// 当前请求的config投递至刷新后的任务队列中
refreshTokenArray.push(() => {
resolve(request(error.config))
})
})
}
}
//... 其他异常捕获
})
虽然token大家平常工作中都会使用,但是我见过太多的项目token使用上存在误区。例如为了避免token过期问题,让token的有效期为一周,还有些人甚至设置了一年(手动滑稽)。还有一些人只设置了拦截器,例如请求发现token过期或者后端返回了401,直接让用户跳转至登录页面,这样的用户体验真的很不优化。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。