优化封装本地存储TOKEN操作模块
基本步骤
- 创建
src/utils/storage.js模块
// 获取数据
export const getItem = key => {
const data = window.localStorage.getItem(key)
// 用try catch来进行 json的转换
try {
return JSON.parse(data)
} catch (err) {
return data
}
}
// 存储数据
export const setItem = (key, value) => {
// 将对象类型的数据 转换为json字符串格式
if (typeof value === 'object') {
value = JSON.stringify(value)
}
window.localStorage.setItem(key, value)
}
// 清除数据
export const removeItem = key => {
window.localStorage.removeItem(key)
}
- 在
store/index.js引入方法,并使用
import { getItem, setItem } from '@/utils/storage.js'
// 定义key的名字
const TOKEN = 'TOUTIAO_USER'
...
export default new Vuex.Store({
state: {
// 获取本地数据
user: getItem(TOKEN)
},
mutations: {
setUser (state, data) {
state.user = data
// 保存本地数据
setItem(TOKEN)
}
}
})
- 在
src/views/login/index.vue中,登录成功后存入token
methods: {
// 提交 登录表单
async onSubmit() {
...
// 3.发送请求
// 在aixos的 try 代码快中,只要响应状态码不是 4/5 开头,都会正常执行,
// 否则会去catch代码块中
try {
const {
data: { data }
} = await login(user)
// data--{ token: 'xxx', refresh_token: 'xxx' }
// 更新store中的数据
this.$store.commit('setUser', data)
...
} catch (err) {
...
}
},
...
}
- 在
utils/request.js中,通过请求拦截器--设置统一的headers(token)
import axios from 'axios'
import store from '@/store'
// axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
// 创建axios实例,并设置配置项目(不同的基地址/参数)
const request1 = axios.create({
baseURL: 'http://toutiao-app.itheima.net'
// baseURL: 'http://ttapi.research.itcast.cn/' // 基础路径
})
// 请求拦截器--设置统一的headers
// Add a request interceptor
request1.interceptors.request.use(
function(config) {
// Do something before request is sent
// 发起请求会经过这里
// config :本次请求的配置对象
// config 里面有一个属性:headers
const { user } = store.state
// 先判断 有没有token(如登录时,就没有token,就不需要添加token)
if (user && user.token) {
config.headers.Authorization = `Bearer ${user.token}`
}
return config
},
function(error) {
// Do something with request error
// 请求出错了,还没有发出去
return Promise.reject(error)
}
)
export default request1
关于TOKEN过期问题
问题阐述
登录成功之后后端会返回两个 Token:
token:访问令牌,有效期2小时refresh_token:刷新令牌,有效期14天,用于访问令牌过期之后重新获取新的访问令牌
token超过有效期服务端会返回 401 表示 Token 无效或过期了。
为什么过期时间这么短?
- 为了安全,例如 Token 被别人盗用
过期了怎么办?
让用户重新登录,用户体验太差了- 使用
refresh_token解决token过期
解决办法
当用户的
token过期时,拿着refresh_token去换取新的token,并及时更新store和本地的token信息,来保持用户的登陆状态
处理流程:
-
在axios的响应拦截器
axios.interceptors.request.use()中加入token刷新逻辑- 判断错误状态码
err.response.status是否为401,如果是说明token过期了,进行处理
- 判断错误状态码
-
判断此时store(本地)中是否有refresh_token
-
如果没有,跳转到login页重新登录
router.push('/login') -
如果有,则从store.state中获取到
refresh_token
-
-
将
refresh_token放到请求头的Authorization中,向服务器获取新的 token- 注意:此时直接用aixos请求,避免被request1请求拦截器中headers的Authorization覆盖
-
获取新的token成功后,将store和本地存储中将旧的token替换为新的token
-
如果更新token成功,把之前失败的用户请求继续发出去
-
如果失败,直接跳转 登录页
-
src/utils/request.js
/**
* 封装 axios 请求模块
*/
import axios from 'axios'
import store from '@/store'
// 处理大数据 的插件
import jsonBig from 'json-bigint'
import router from '@/router'
// axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
// 创建axios实例,并设置配置项目(不同的基地址/参数)
const request1 = axios.create({
baseURL: 'http://toutiao-app.itheima.net', // 基础路径
/**
* 配置处理后端返回数据中超出 js 安全整数范围问题
*/
// transformResponse 允许自定义原始的响应数据(字符串)
transformResponse: [
function(data) {
try {
// 如果转换成功则返回转换的数据结果
return jsonBig.parse(data)
} catch (err) {
// 如果转换失败,则包装为统一数据格式并返回
return { data }
}
}
]
})
// 请求拦截器--设置统一的headers---------------------------------------------------------------
// Add a request interceptor
request1.interceptors.request.use(
function(config) {
// Do something before request is sent
// 发起请求会经过这里
// config :本次请求的配置对象
// config 里面有一个属性:headers
const { user } = store.state
// a.先判断 有没有token(如登录时,就没有token,就不需要添加token)
// b.且判断 请求头有没有Authorization这个属性,如果有了就不要再重新设置
// 获取新的token时,需要携带refresh_token请求,而不是token
if (user && user.token) {
config.headers.Authorization = `Bearer ${user.token}`
}
return config
},
function(error) {
// Do something with request error
// 请求出错了,还没有发出去
return Promise.reject(error)
}
)
// 设置响应拦截器---------------------------------------------------------------
request1.interceptors.response.use(
// 响应成功进入第1个函数
// 该函数的参数是响应对象
config => {
// console.log('响应拦截器:', config)
return config
},
// 响应失败进入第2个函数,该函数的参数是错误对象
async err => {
// 如果响应码是 401 ,则请求获取新的 token
// 响应拦截器中的 error 就是那个响应的错误对象
// console.dir(err)
if (err.response && err.response.status === 401) {
// 校验是否有 refresh_token
const { user } = store.state
// a. 如果没有就 跳转到登录页面,重新登录
if (!user || !user.refresh_token) {
return router.push('/login')
}
// b. 如果有refresh_token,则请求获取新的 token
try {
// b.1 如果更新token成功,把之前失败的用户请求继续发出去
await getNewToken(user)
// config 是一个对象,其中包含本次失败请求相关的那些配置信息,例如 url、method 都有
// return 把 request 的请求结果继续返回给发请求的具体位置
return request1(err.config)
} catch (error) {
// b.2 如果更新token失败,直接跳转 登录页
console.log('请求刷新token出错了' + error)
router.push('/login')
}
return Promise.reject(err)
}
}
)
// 请求新的token
async function getNewToken(user) {
// 发送请求新的 token的请求
const { data } = await axios({
url: 'http://toutiao-app.itheima.net/v1_0/authorizations',
method: 'PUT',
headers: {
Authorization: `Bearer ${user.refresh_token}`
}
})
// 判断请求是否成功
if (data.message === 'OK') {
// 拿到新的token ,存到store和本地里
const newToken = data.data.token
// 调用store用的方法,更新token
store.commit('updateToken', newToken)
}
}
export default request1
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 引入本地存储方法
import { getItem, setItem } from '@/utils/storage.js'
const TOKEN = 'TOUTIAO_USER'
Vue.use(Vuex)
/*
## 本地存储:
1. 获取麻烦(不同组件访问数据时)
2. 数据不是响应式(比如刷新页面等业务时,需要重新获取token)
## 登录成功,将 Token 存储到 Vuex 容器中
1. 获取方便(不通组件可以共享访问vuex中的数据)
2. 响应式(一旦vuex中数据改变,会通知组件更新)
另外:为了持久化,还需要把 Token 放到本地存储
*/
export default new Vuex.Store({
state: {
// 用户的登录状态信息token
// 获取本地数据
user: getItem(TOKEN) || {}
},
mutations: {
setUser(state, user) {
// 更新 state中的user
state.user = user
// 保存本地数据
setItem(TOKEN, user)
},
updateToken(state, newToken) {
// 更新 state中的token
state.user.token = newToken
// 保存到本地数据
setItem(TOKEN, state.user)
}
},
actions: {},
modules: {}
})