Vuex处理用户Token
下周六 人气:01. 处理用户 Token
Token 是用户登录成功之后服务端返回的一个身份令牌,在项目中的多个业务中需要使用到:
- 访问需要授权的 API 接口
- 校验页面的访问权限
- ...
问题:Token往哪儿存?
我们只有在第一次用户登录成功之后才能拿到 Token。所以为了能在其它模块中获取到 Token 数据,我们需要把它存储到一个公共的位置,方便随时取用。
本地存储
- 获取麻烦
- 数据不是响应式
Vuex 容器(推荐)
- 获取方便
- 响应式的
使用容器存储 Token 的思路:
登录成功,将 Token 存储到 Vuex 容器中
- 获取方便
- 响应式
为了持久化,还需要把 Token 放到本地存储
- 持久化
总结: Vuex状态管理工具可有可无 (*・ω-q)
在 src/store/index.js
中
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ // 1. 存储数据的地方 - 类比于vue文件的data() state: { // 一个对象,储存当前登录用户的token数据 user: {} }, // 2. 外界修改store中state的属性值,必须通过mutations中设置的修改方法 - 类比methods // 注意:这里方法里面的代码和.vue文件中的书写方式有差异,注意区分 mutations: { setUser (state, data) { state.user = data } }, // 3. 涉及到异步操作后修改state数据时,必须先过actions中的自定义方法,通过actions去调用mutations中的方法 actions: { }, // 4. 是state中数据的计算属性 - 类比computed getters: { }, // 5. 模块化vuex,可以让每一个模块拥有自己的 state、mutation、action、 getters,使得结构非常清晰,方便管理。 modules: { } })
登录成功以后将后端返回的 token 调用commit方法存到store中
async onSubmit () { ... try { const res = await loginAPI(user) console.log('登录成功', res) // 调用store中的方法,将接口返回的token存到状态管理器中 this.$store.commit('setUser', res.data.data) // 提示 success 或者 fail 的时候,会先把其它的 toast 先清除 this.$toast.success('登录成功') } catch (err) { ... },
3. 将 store中的 token 相关数据存储到容器中
const TOKEN_KEY = 'TOUTIAO_USER' export default new Vuex.Store({ state: { user: JSON.parse(window.localStorage.getItem(TOKEN_KEY)) }, mutations: { setUser (state, data) { state.user = data // 为了防止刷新丢失,需要把数据备份到本地存储 window.localStorage.setItem(TOKEN_KEY, JSON.stringify(state.user)) } }, ... })
2. 优化封装本地存储操作模块 - 封装localStrage功能
创建 src/utils/storage.js
模块
- 存储
- 获取
- 删除
// 封装本地存储操作模块 /* 一个本地存储的数据应该拥有那些特性: 增删改查 */ /* 储存数据 (新增, 修改) */ export const setItem = (key, value) => { // 将数组,对象等引用数据类型转化为JSON字符串进行存储 // 将简单数据类型直接存储 // 需要外界使用该方法时传入对一个的 键名 if (typeof value === 'object') { // 将数组,对象等引用数据类型转化为JSON字符串进行存储 value = JSON.stringify(value) } window.localStorage.setItem(key, value) } /* 获取数据 */ export const getItem = key => { // 如果该键存储的是引用数据类型的JSON字符串,那么需要进行JSON.parse的转化 const data = window.localStorage.getItem(key) // 使用JSON.parse()做JSON数据转化时可能会出现报错 // 1. 做条件判断(要去找到所有满足、不满足的条件) 2. 做错误判断 try { // 先尝试做JSON.parse()的转化,如果报错了,在把他当成原始数据进行返回 return JSON.parse(data) } catch (error) { return data } } /* 删除缓存数据 */ export const removeItem = key => { window.localStorage.removeItem(key) }
在store/index.js引入方法
import { getItem, setItem } from '../utils/storage.js'
使用方法
import { getItem, setItem } from '../utils/storage.js' const TOKEN_KEY = 'TOUTIAO_USER' export default new Vuex.Store({ state: { user:getItem(TOKEN_KEY) }, mutations: { setUser (state, data) { state.user = data // 为了防止刷新丢失,需要把数据备份到本地存储 setItem(TOKEN_KEY, state.user) } }, ... })
3. Vuex各属性的使用
创建测试用store.js
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ // state存放状态, state: { name: 'tom', // 需要共用的数据 age: '22' }, // getter为state的计算属性 getters: { getName: (state) => state.name, // 获取name getAge: (state) => state.age }, // mutations可更改状态的逻辑,同步操作 mutations: { setName: (state, data) => { state.name = data }, setAge: (state, data) => { state.age = data } }, // 提交mutation,异步操作 actions: { acSetName (context, name) { setTimeout(() => { // 延时1秒提交至mutations中的方法 context.commit('setName', name) }, 1000) }, acSetAge (context, age) { setTimeout(() => { context.commit('setAge', age) }, 1000) } }, // 将store模块化 modules: { } })
创建页面comOne.vue
测试计算属性
<template> <div class="wrapper"> asd <!-- 读取mapGetters中的getName与getAge --> <div> name:<span>{{ getName }}</span> </div> <div> age:<span>{{ getAge }}</span> </div> </div> </template> <script> import { mapState, mapGetters } from 'vuex' // 导入vuex的辅助函数 export default { components: {}, // 计算属性computed无法传递参数 computed: { // 映射 state 中的数据为计算属性 ...mapState(['name', 'age']), // 映射 getters 中的数据为计算属性 ...mapGetters(['getName', 'getAge']) } } </script> <style scoped> </style>
创建comTwo.vue
测试同步异步方法
<template> <div class="wrapper"> <div> <span>同步修改:</span> <!--直接回车调用mapMutations中的setName方法与setAge方法--> <input v-model="nameInp" @keydown.enter="setName(nameInp)" placeholder="同步修改name" /> <input v-model="ageInp" @keydown.enter="setAge(ageInp)" placeholder="同步修改age" /> </div> <div> <span>异步修改:</span> <!--直接回车调用mapAtions中的acSetName方法与acSetAge方法--> <input v-model="acNameInp" @keydown.enter="acSetName(acNameInp)" placeholder="异步修改name" /> <input v-model="AcAgeInp" @keydown.enter="acSetAge(AcAgeInp)" placeholder="异步修改age" /> </div> </div> </template> <script> import { mapMutations, mapActions } from 'vuex' // 导入vuex的辅助函数 export default { components: {}, data () { return { nameInp: '', // 绑定输入框的值 ageInp: '', acNameInp: '', AcAgeInp: '' } }, methods: { // 用于生成与 mutations 对话的方法,即:包含 $store.commit(xx) 的函数 ...mapMutations(['setName', 'setAge']), // 用于生成与 actions 对话的方法,即:包含 $store.dispatch(xx) 的函数 ...mapActions(['acSetName', 'acSetAge']) } } </script> <style scoped> </style>
4. 关于 Token 过期问题
登录成功之后后端会返回两个 Token:
token
:访问令牌,有效期2小时refresh_token
:刷新令牌,有效期14天,用于访问令牌过期之后重新获取新的访问令牌
我们的项目接口中设定的 Token
有效期是 2 小时,超过有效期服务端会返回 401
表示 Token 无效或过期了。
为什么过期时间这么短?
- 为了安全,例如 Token 被别人盗用
过期了怎么办?
- 让用户重新登录,用户体验太差了
- 使用
refresh_token
解决token
过期
如何使用 refresh_token
解决 token
过期?
到课程的后面我们开发的业务功能丰富起来之后,再给大家讲解 Token 过期处理。
大家需要注意的是在学习测试的时候如果收到 401 响应码,请重新登录。
5.优化设置 Token
项目中的接口除了登录之外大多数都需要提供 token 才有访问权限。
通过接口文档可以看到,后端接口要求我们将 token 放到请求头 Header
中并以下面的格式发送。
字段名称:Authorization
字段值:Bearer token
,注意 Bearer
和 token
之间有一个空格
方式一:在每次请求的时候手动添加(麻烦)。
axios({ method: "", url: "", headers: { Authorization: "Bearer token" } })
方式二:使用请求拦截器统一添加(推荐,更方便)。
在 src/utils/request.js
中添加拦截器统一设置 token:
import axios from 'axios' import store from '../store/index.js' const request = axios.create({ baseURL: 'http://toutiao.itheima.net/' // 接口的基准路径 }) // 请求拦截器 // Add a request interceptor request.interceptors.request.use(function (config) { // Do something before request is sent // config :本次请求的配置对象 // config 里面有一个属性:headers const { user } = store.state if (user && user.token) { config.headers.Authorization = `Bearer ${user.token}` } return config }, function (error) { // 如果请求出错 - 抛出异常 // Do something with request error return Promise.reject(error) })
api.user.js
注释掉store和获取用户信息携带的请求头
import request from '@/utils/request' // import store from '@/store' /** * 获取用户自己的信息 */ export const getUserInfo = () => { return request({ method: 'GET', url: '/v1_0/user' // 发送请求头数据 // headers: { // // 注意:该接口需要授权才能访问 // // token的数据格式:Bearer token数据,注意 Bearer 后面有个空格 // Authorization: `Bearer ${store.state.user.token}` // } }) }
加载全部内容