Vue axios Get token Temporary Token Encapsulation Case
- 2021-08-16 22:54:56
- OfStack
Preface
Why do you have to write this blog? Because of this, I have a kind of excellent pain in my balls. Don't ask the rest, I won't tell you anyway. Because I don't even want to draw the flow chart.
Development architecture
Front Page: Vue
Network request: Axios;; Mode: vue add axios
Caching scheme
Global variable: Vuex
Local cache: LocalStorage
Technology dependence
You guess?
Background
The company develops an Web page embedded in App, and uses the old routine in security: App transmits parameters to the front end (including signature) through URL, and the front end transmits parameters to the back end of H5 for verification, and then the front end decides whether the user is legal or not. In addition, the front end of N JS method is defined to judge whether it is called by Android or Apple according to the fixed GET parameters.
Preliminary assumption
The preliminary idea of token design scheme is as follows: get token when entering for the first time, and check whether the signature passes or not at the back end. If you don't pass, please enter the page through legal channels and don't disappear.
Otherwise, the user can continue the follow-up operation until the back end returns the token expired specific status code, and the front end calls the JS method to re-obtain the URL parameter request token without the user's feeling, and continues the user's request operation after the completion. (To prevent users from manipulating data elsewhere using the old token, each time an token is fetched and validated from the App, instead of refreshing and returning the new token in the interface.)
Eggache matters
There is no version control when defining URL parameters in Phase 1, which leads to no response when the front-end new page calls the unknown method page when adding the iterative version of JS method in Phase 2; I don't know how many periods are buried data …
In order to avoid the phenomenon that one request changes to three requests due to the expiration of token in the request process, it is necessary to check the asynchronous method of token aging before calling each request (if token expires, call getToken to obtain a new token and store it locally), which leads to block nesting.
After encapsulating N methods, I won't say …
Upgrade assumption
Version of what this aside, on this token problem I can not add a request every time copy and paste copy and paste it? It can be annoying! Then I can only judge the timeliness of token before axios request.
Get to the point
Function declaration
getToken: Fetch stored token locally
checkToken: Check the aging of token. If the failure call refreshToken function is successful, it will be stored locally, otherwise it will return the error reason
refreshToken: Call JS method to get signature parameters from App and re-request token
Matters needing attention
When the token expires during the checkToken process, the local expired token cache data is removed first.
/* 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);
// When initiating a request, cancel the same request that is currently in progress
if (promiseArr[config.url]) {
promiseArr[config.url](" Please wait a moment ");
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) => {
// Terminate this request
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 And ------------------------------------------------------------------ . ",
// `color:${randomColor};`
// );
// console.log("| Request address: ", response.config.url);
// console.log("| Request parameters: ", response.config.data);
// console.log("| Return data: ", response.data);
// console.log(
// "%c Phil ------------------------------------------------------------------ Recall ",
// `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(' Interface 2 Secondary request result:', result);
if (err_check(status, statusText, data) && data) {
return Promise.resolve(data)
} else {
return Promise.reject(Error(data.resDesc));
}
}).catch((err) => {
// console.log(' Interface 2 Secondary request err:' + err);
return Promise.reject(err);
});
} else {
// alert("result:" + result)
return Promise.reject(Error(data.resDesc))
}
}).catch((err) => {
// Terminate this request
// alert(" Terminate this request :" + 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-vars
const 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;
Supplementary knowledge: vue+axios+token package axios package interface url, with token request, token failure refresh
1. Encapsulate axios
import axios from 'axios'
import qs from "qs"
const TIME_OUT_MS = 60 * 1000 // Default request timeout
//axios.defaults.baseURL = 'http://localhost:8080';
// http request Interceptor
axios.interceptors.request.use(
config => {
if ($cookies.get("access_token")) { // Judge whether it exists or not token , if any, then each http header Add them all token
config.headers.Authorization ='Bearer '+ $cookies.get("access_token");
}
return config;
},
err => {
return Promise.reject(err);
});
// http response Interceptor
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 Expired ");
var config = error.config;
refresh(config);
return;
}
}
return Promise.reject(error) // Returns the error message returned by the interface
});
/*
* Refresh 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){ // Save again token
$cookies.set("access_token",result.data.access_token);
$cookies.set("refresh_token",result.data.refresh_token);
// Need to be re-executed
axios(config);
}else{
//this.$events.emit('goto', 'login');
window.location.reload();
}
}
).catch((error) => {
//this.$events.emit('goto','login');
window.location.reload();
});
}
/*
* @param response Returns a list of data
*/
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 Is the of the interface ip Prefixes, such as http:10.100.1.1:8989/
// return url
// }
/*
* @param data Parameter list
* @return
*/
function handleParams (data) {
return qs.stringify(data);
}
export default {
/*
* @param url
* @param data
* @param response Callback function on successful request
* @param exception Callback function of exception
*/
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 Request
* @param url
* @param response Callback function on successful request
* @param exception Callback function of exception
*/
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)
}
}
)
}
}
2. Configure axios across domains and request 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')
// Introducing cross-domain configuration
var proxyConfig = require('./proxyConfig')
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
//proxyTable: {}, // The default cross-domain configuration is empty
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',
// This needs to change when the project name changes Originally 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. Create a file under the config directory proxyConfig. js file
module.exports={
proxy:{
'/':{ // Will localhost : 8081 Mapped to /apis
target:'http://localhost:8080',// Interface address
changeOrigin: true,// This parameter configuration is required if the interface is cross-domain
secure:false, // If the interface is HTTPS Interface, which needs to be set to true
pathRewrite:{
'^/':''
}
}
}
}
3. Encapsulate API Request Url port. js
export default {
oauth: {
login: '/oauth/token', // Login
logout: '/oauth/logout' // // Quit
},
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'
}
}
4. Introduction of main. js
import http from './plugins/http.js'
import ports from './plugins/ports'
Vue.prototype.http = http
Vue.prototype.ports = ports
Step 5 Use
Used in login. vue
login() {
this.http.post(this.ports.oauth.login,{username:this.userId,
password:this.password,grant_type:'password'}, res => {
if (res.success) {
// Returns the correct processing
Page jump
this.$events.emit('goto', 'edit');
} else {
// Handling of Returns Errors
//alert(" Waiting for processing ");
}
},err =>{
//console.log(" Processing "+err.response.status);
if(err.response.status=='400'){
// Display user name or password error
this.$refs.username.focus();
this.$refs.hint.click();
}
})
}