首頁 > 軟體

Vue axios獲取token臨時令牌封裝案例

2020-09-11 18:01:02

前言

為什麼非要寫這個部落格呢?因為這件事讓我有一種蛋蛋的優疼。剩下的都別問,反正問我也不會說。因為流程圖我都不想(懶得)畫。

開發架構

前端頁面:Vue

網路請求:Axios;方式:vue add axios

快取方案

全域性變數:Vuex

本地快取:LocalStorage

技術依賴

你猜?

背景

公司開發一個嵌入App的Web頁面,安全方面使用老套路:App通過URL傳參給前端(包含簽名),前端把引數透傳給H5後端驗籤,完事兒之後前端再決定使用者是否合法。另外定義了N個JS方法前端根據固定GET引數判斷是安卓還是蘋果來呼叫。

初步設想

關於token設計方案的初步設想是這樣的:第一次進入的時候獲取token,後端檢查簽名是否通過。不通過則彈框請從合法途徑進入頁面並且不消失。

否則就可以讓使用者繼續後續操作,直到後端返回token過期特定狀態碼回來前端在使用者無感的情況下呼叫JS方法重新獲取URL引數請求token,完事兒之後繼續使用者的請求操作。(為避免使用者使用舊token在其他地方運算元據,每次獲取token都重新從App中獲取並驗證,而不是在介面中重新整理並返回新的token)

蛋疼事項

一期的時候定義URL引數時沒有版本控制,導致二期新增JS方法迭代版本時前端新增頁面呼叫了未知方法頁面毫無反應;埋點資料也不知道是幾期的…

為儘量避免請求過程中出現token過期導致的1次請求變3次請求現象每次呼叫請求之前需要先檢查token時效的非同步方法(如果token過期則呼叫getToken獲取新的token並儲存在本地)導致block巢狀。

後面又封裝了N個方法就不說了…

升級設想

版本什麼的這個先不說,就這個token問題我總不能每次新增一個請求就複製貼上覆制貼上的吧?能煩死人!那我只能在axios請求之前判斷token時效性啦。

直奔主題

函數宣告

getToken:從本地取已儲存token

checkToken:檢查token時效,失效呼叫refreshToken函數成功則儲存本地,否則返回錯誤原因

refreshToken:呼叫JS方法從App獲取簽名引數重新請求token

注意事項

在checkToken過程中token過期時,先移除本地已過期token快取資料。

/* 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);
  //發起請求時,取消掉當前正在進行的相同請求
  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) => {
      // 終止這個請求
      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("| 請求引數:", response.config.data);
   // console.log("| 返回資料:", 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) => {
      // 終止這個請求
      // alert("終止這個請求:" + 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;

補充知識:vue+ axios+token 封裝axios 封裝介面url,帶token請求,token失效重新整理

一、封裝axios

import axios from 'axios'
import qs from "qs" 
const TIME_OUT_MS = 60 * 1000 // 預設請求超時時間
//axios.defaults.baseURL = 'http://localhost:8080'; 
 
// http request 攔截器
axios.interceptors.request.use(
  config => {
    if ($cookies.get("access_token")) { // 判斷是否存在token,如果存在的話,則每個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)  // 返回介面返回的錯誤資訊
  });
/*
*重新整理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);
        //需要重新執行
        axios(config);
      }else{ 
 
        //this.$events.emit('goto', 'login');
        window.location.reload();
      }
    }
  ).catch((error) => {
    //this.$events.emit('goto','login');
    window.location.reload();
  });
}
/*
* @param response 返回資料列表
*/
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 參數列
* @return
*/
function handleParams (data) {
  return qs.stringify(data);
} 
export default {
  /*
   * @param url
   * @param data
   * @param response 請求成功時的回撥函數
   * @param 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 請求
   * @param url
   * @param response 請求成功時的回撥函數
   * @param 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)
        }
      }
    )
  }
}

二、設定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: {}, //預設跨域設定為空
    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',
    // 專案名字改變時這裡需要變化 原先為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目錄下建立一個檔案 proxyConfig.js檔案

module.exports={
  proxy:{
    '/':{ //將localhost:8081 對映為 /apis
      target:'http://localhost:8080',//介面地址
      changeOrigin: true,// 如果介面跨域,需要進行這個引數設定
      secure:false, //如果介面是HTTPS介面,需要設定成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 = http
Vue.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) {
    // 返回正確的處理
    頁面跳轉
    this.$events.emit('goto', 'edit');
  } else {
    // 返回錯誤的處理
    //alert("等待處理");
  }
},err =>{
    //console.log("正在處理"+err.response.status);
    if(err.response.status=='400'){
      //顯示使用者名稱或密碼錯誤
      this.$refs.username.focus();
      this.$refs.hint.click();
    }
  })
   
}

以上這篇Vue axios獲取token臨時令牌封裝案例就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com