Vue axios獲取token臨時(shí)令牌封裝案例
前言
為什么非要寫這個(gè)博客呢?因?yàn)檫@件事讓我有一種蛋蛋的優(yōu)疼。剩下的都別問,反正問我也不會說。因?yàn)榱鞒虉D我都不想(懶得)畫。
開發(fā)架構(gòu)
前端頁面:Vue
網(wǎng)絡(luò)請求:Axios;方式:vue add axios
緩存方案
全局變量:Vuex
本地緩存:LocalStorage
技術(shù)依賴
你猜?
背景
公司開發(fā)一個(gè)嵌入App的Web頁面,安全方面使用老套路:App通過URL傳參給前端(包含簽名),前端把參數(shù)透傳給H5后端驗(yàn)簽,完事兒之后前端再決定用戶是否合法。另外定義了N個(gè)JS方法前端根據(jù)固定GET參數(shù)判斷是安卓還是蘋果來調(diào)用。
初步設(shè)想
關(guān)于token設(shè)計(jì)方案的初步設(shè)想是這樣的:第一次進(jìn)入的時(shí)候獲取token,后端檢查簽名是否通過。不通過則彈框請從合法途徑進(jìn)入頁面并且不消失。
否則就可以讓用戶繼續(xù)后續(xù)操作,直到后端返回token過期特定狀態(tài)碼回來前端在用戶無感的情況下調(diào)用JS方法重新獲取URL參數(shù)請求token,完事兒之后繼續(xù)用戶的請求操作。(為避免用戶使用舊token在其他地方操作數(shù)據(jù),每次獲取token都重新從App中獲取并驗(yàn)證,而不是在接口中刷新并返回新的token)
蛋疼事項(xiàng)
一期的時(shí)候定義URL參數(shù)時(shí)沒有版本控制,導(dǎo)致二期新增JS方法迭代版本時(shí)前端新增頁面調(diào)用了未知方法頁面毫無反應(yīng);埋點(diǎn)數(shù)據(jù)也不知道是幾期的…
為盡量避免請求過程中出現(xiàn)token過期導(dǎo)致的1次請求變3次請求現(xiàn)象每次調(diào)用請求之前需要先檢查token時(shí)效的異步方法(如果token過期則調(diào)用getToken獲取新的token并存儲在本地)導(dǎo)致block嵌套。
后面又封裝了N個(gè)方法就不說了…
升級設(shè)想
版本什么的這個(gè)先不說,就這個(gè)token問題我總不能每次新增一個(gè)請求就復(fù)制粘貼復(fù)制粘貼的吧?能煩死人!那我只能在axios請求之前判斷token時(shí)效性啦。
直奔主題
函數(shù)聲明
getToken:從本地取已存儲token
checkToken:檢查token時(shí)效,失效調(diào)用refreshToken函數(shù)成功則存儲本地,否則返回錯(cuò)誤原因
refreshToken:調(diào)用JS方法從App獲取簽名參數(shù)重新請求token
注意事項(xiàng)
在checkToken過程中token過期時(shí),先移除本地已過期token緩存數(shù)據(jù)。
/* eslint-disable no-console *//* eslint-disable no-unused-vars */'use strict';import Vue from ’vue’;import axios from 'axios';import { getToken } from ’../utils/storage.js’import { checkToken, refreshToken, clearCache } from '../utils/utils.js';// Full config: https://github.com/axios/axios#request-config// axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || ’’;// axios.defaults.headers.common[’Authorization’] = AUTH_TOKEN;// axios.defaults.headers.post[’Content-Type’] = ’application/x-www-form-urlencoded’;axios.defaults.headers.post['Content-Type'] = 'application/json';let cancel, promiseArr = {};let config = { baseURL: process.env.VUE_APP_BASE_URL, timeout: 8 * 1000, // Timeout withCredentials: true, // Check cross-site Access-Control};const _axios = axios.create(config);_axios.interceptors.request.use( function (config) { // Do something before request is sent let token = getToken(); // alert('token1:' + token); //發(fā)起請求時(shí),取消掉當(dāng)前正在進(jìn)行的相同請求 if (promiseArr[config.url]) { promiseArr[config.url]('請稍后'); promiseArr[config.url] = cancel; } else { promiseArr[config.url] = cancel; } if (token) { return checkToken(null) .then((result) => { // console.log('refreshToken result:', result); if (result === true) { token = getToken() // alert('token2:' + token); config.headers.common['authorization'] = token; return config; } else { return Promise.reject(Error(result)) } }).catch((err) => { // 終止這個(gè)請求 return Promise.reject(err); }); } return config; }, function (error) { // Do something with request error return Promise.reject(error); });// Add a response interceptor_axios.interceptors.response.use( function (response) { // Do something with response data let { status, statusText, data } = response; if (err_check(status, statusText, data) && data) { // var randomColor = `rgba(${parseInt(Math.random() * 255)},${parseInt( // Math.random() * 255 // )},${parseInt(Math.random() * 255)})`; // console.log( // '%c┍------------------------------------------------------------------┑', // `color:${randomColor};` // ); // console.log('| 請求地址:', response.config.url); // console.log('| 請求參數(shù):', response.config.data); // console.log('| 返回?cái)?shù)據(jù):', response.data); // console.log( // '%c┕------------------------------------------------------------------┙', // `color:${randomColor};` // ); if (data.resCode === '0001') { clearCache() var config = response.config; var url = config.url; url = url.replace('/apis', '').replace(process.env.VUE_APP_BASE_URL, '') config.url = url; // alert(JSON.stringify(config)) return refreshToken(null) .then((result) => { // console.log('refreshToken result:', result); if (result == true) { let token = getToken() if (token) {config.headers['authorization'] = token; } return axios(config).then((result) => {let { status, statusText, data } = result;// console.log(’接口二次請求 result:’, result);if (err_check(status, statusText, data) && data) { return Promise.resolve(data)} else { return Promise.reject(Error(data.resDesc));} }).catch((err) => {// console.log(’接口二次請求 err:’ + err);return Promise.reject(err); }); } else { // alert('result:' + result) return Promise.reject(Error(data.resDesc)) } }).catch((err) => { // 終止這個(gè)請求 // alert('終止這個(gè)請求:' + err.message) // console.log('refreshToken err:', err); return Promise.reject(err); }); } else { return Promise.resolve(data); } } else { return Promise.reject(Error(statusText)); } // return response; }, function (error) { // Do something with response error // console.log('error', error); return Promise.reject(error); });// eslint-disable-next-line no-unused-varsconst err_check = (code, message, data) => { if (code == 200) { return true; } return false;};Plugin.install = function (Vue, options) { Vue.axios = _axios; window.axios = _axios; Object.defineProperties(Vue.prototype, { axios: { get() { return _axios; } }, $axios: { get() { return _axios; } }, });};Vue.use(Plugin)export default Plugin;
補(bǔ)充知識:vue+ axios+token 封裝axios 封裝接口url,帶token請求,token失效刷新
一、封裝axios
import axios from ’axios’import qs from 'qs' const TIME_OUT_MS = 60 * 1000 // 默認(rèn)請求超時(shí)時(shí)間//axios.defaults.baseURL = ’http://localhost:8080’; // http request 攔截器axios.interceptors.request.use( config => { if ($cookies.get('access_token')) { // 判斷是否存在token,如果存在的話,則每個(gè)http header都加上token config.headers.Authorization =’Bearer ’+ $cookies.get('access_token'); } return config; }, err => { return Promise.reject(err);}); // http response 攔截器axios.interceptors.response.use( response => { return response; }, error => { console.log('response error :'+error); if (error.response) { switch (error.response.status) {case 401: console.log('token 過期'); var config = error.config; refresh(config); return; } } return Promise.reject(error) // 返回接口返回的錯(cuò)誤信息 });/**刷新token*/function refresh(config){ var refreshToken = $cookies.get('refresh_token'); var grant_type = 'refresh_token'; axios({ method: ’post’, url: ’/oauth/token’, data: handleParams({'grant_type':grant_type,'refresh_token':refreshToken}), timeout: TIME_OUT_MS, headers: {} }).then( (result) => { if(result.data.access_token){ //重新保存token$cookies.set('access_token',result.data.access_token);$cookies.set('refresh_token',result.data.refresh_token);//需要重新執(zhí)行axios(config); }else{ //this.$events.emit(’goto’, ’login’);window.location.reload(); } } ).catch((error) => { //this.$events.emit(’goto’,’login’); window.location.reload(); });}/** @param response 返回?cái)?shù)據(jù)列表*/function handleResults (response) { var result = { success: false, message: ’’, status: [], errorCode: ’’, data: {} } if (response.status == ’200’) { result.status = response.status; result.data = response.data; result.success = true; } return result} // function handleUrl (url) {// //url = BASE_URL + url// url =root +url;// // BASE_URL是接口的ip前綴,比如http:10.100.1.1:8989/// return url// } /** @param data 參數(shù)列表* @return*/function handleParams (data) { return qs.stringify(data);} export default { /* * @param url * @param data * @param response 請求成功時(shí)的回調(diào)函數(shù) * @param exception 異常的回調(diào)函數(shù) */ post (url, data, response, exception) { axios({ method: ’post’, //url: handleUrl(url), url: url, data: handleParams(data), timeout: TIME_OUT_MS, headers: {//’Content-Type’: ’application/json; charset=UTF-8’ } }).then( (result) => {response(handleResults(result)) } ).catch( (error) => {if (exception) { exception(error)} else { console.log(error)} } ) }, /* * get 請求 * @param url * @param response 請求成功時(shí)的回調(diào)函數(shù) * @param exception 異常的回調(diào)函數(shù) */ get (url,data, response, exception) { axios({ method: ’get’, url: url, params:data, timeout: TIME_OUT_MS, headers: {’Content-Type’: ’application/json; charset=UTF-8’ } }).then( (result) => {response(handleResults(result)) } ).catch( (error) => {console.log('error'+response);if (exception) { exception(error)} else { console.log(error)} } ) }}
二、配置axios 跨域,以及請求baseUrl
1.config-->index.js
’’use strict’// Template version: 1.3.1// see http://vuejs-templates.github.io/webpack for documentation. const path = require(’path’) //引入跨域配置var proxyConfig = require(’./proxyConfig’) module.exports = { dev: { // Paths assetsSubDirectory: ’static’, assetsPublicPath: ’/’, //proxyTable: {}, //默認(rèn)跨域配置為空 proxyTable: proxyConfig.proxy, // Various Dev Server settings host: ’localhost’, // can be overwritten by process.env.HOST port: 8886, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined autoOpenBrowser: false, errorOverlay: true, notifyOnErrors: true, poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- /** * Source Maps */ // https://webpack.js.org/configuration/devtool/#development devtool: ’cheap-module-eval-source-map’, // If you have problems debugging vue-files in devtools, // set this to false - it *may* help // https://vue-loader.vuejs.org/en/options.html#cachebusting cacheBusting: true, cssSourceMap: true }, build: { // Template for index.html index: path.resolve(__dirname, ’../dist/index.html’), // Paths assetsRoot: path.resolve(__dirname, ’../dist’), assetsSubDirectory: ’static’, // 項(xiàng)目名字改變時(shí)這里需要變化 原先為assetsPublicPath: ’.’ assetsPublicPath: ’./’, /** * Source Maps */ productionSourceMap: true, // https://webpack.js.org/configuration/devtool/#production devtool: ’#source-map’, // Gzip off by default as many popular static hosts such as // Surge or Netlify already gzip all static assets for you. // Before setting to `true`, make sure to: // npm install --save-dev compression-webpack-plugin productionGzip: false, productionGzipExtensions: [’js’, ’css’], // Run the build command with an extra argument to // View the bundle analyzer report after build finishes: // `npm run build --report` // Set to `true` or `false` to always turn it on or off bundleAnalyzerReport: process.env.npm_config_report }}
2.config目錄下創(chuàng)建一個(gè)文件 proxyConfig.js文件
module.exports={ proxy:{ ’/’:{ //將localhost:8081 映射為 /apis target:’http://localhost:8080’,//接口地址 changeOrigin: true,// 如果接口跨域,需要進(jìn)行這個(gè)參數(shù)配置 secure:false, //如果接口是HTTPS接口,需要設(shè)置成true pathRewrite:{’^/’:’’ } } }}
三、封裝API 請求Url port.js
export default { oauth: { login: ’/oauth/token’, // 登錄 logout: ’/oauth/logout’ // // 退出 }, user: { addUser: ’/user/add’, updateUser: ’/user/update’, getUser:’/user/’, //+ Id exists:’/exists/’, // +id enable:’/enable/’, // +id disable:’/disable/’, // +id delete:’/delete/’, //+id password:’/password ’, query:’/query’ }}
四、main.js 引入
import http from ’./plugins/http.js’import ports from ’./plugins/ports’Vue.prototype.http = httpVue.prototype.ports = ports
五、使用
login.vue中使用
login() { this.http.post(this.ports.oauth.login,{username:this.userId, password:this.password,grant_type:’password’}, res => { if (res.success) { // 返回正確的處理 頁面跳轉(zhuǎn) this.$events.emit(’goto’, ’edit’); } else { // 返回錯(cuò)誤的處理 //alert('等待處理'); }},err =>{ //console.log('正在處理'+err.response.status); if(err.response.status==’400’){ //顯示用戶名或密碼錯(cuò)誤 this.$refs.username.focus(); this.$refs.hint.click(); } }) }
以上這篇Vue axios獲取token臨時(shí)令牌封裝案例就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持好吧啦網(wǎng)。
相關(guān)文章:
1. idea設(shè)置提示不區(qū)分大小寫的方法2. IntelliJ IDEA設(shè)置默認(rèn)瀏覽器的方法3. HTTP協(xié)議常用的請求頭和響應(yīng)頭響應(yīng)詳解說明(學(xué)習(xí))4. .NET SkiaSharp 生成二維碼驗(yàn)證碼及指定區(qū)域截取方法實(shí)現(xiàn)5. IntelliJ IDEA創(chuàng)建web項(xiàng)目的方法6. CentOS郵件服務(wù)器搭建系列—— POP / IMAP 服務(wù)器的構(gòu)建( Dovecot )7. VMware中如何安裝Ubuntu8. docker容器調(diào)用yum報(bào)錯(cuò)的解決辦法9. springboot集成與使用Sentinel的方法10. django創(chuàng)建css文件夾的具體方法
