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();
    }
  })
   
}

Related articles: