無(wú)痛刷新token續(xù)接401請(qǐng)求

在小程序開(kāi)發(fā)中,我們都知道小程序是沒(méi)有cookie的,那么用戶身份是如何確定的,后段頒發(fā)token,前端每次請(qǐng)求頭部附帶token。

既然是token,那么肯定有它的過(guò)期時(shí)間,沒(méi)有一個(gè)token是永久的,永久的token就相當(dāng)于一串永久的密碼,是不安全的,

那么既然有刷新時(shí)間,問(wèn)題就來(lái)了

1.前后端交互的過(guò)程中token如何存儲(chǔ)?

2.token過(guò)期時(shí),前端該怎么處理?

3.當(dāng)用戶正在操作時(shí),遇到token過(guò)期該怎么辦?直接跳回登陸頁(yè)面?

token如何存儲(chǔ)?

cookie的大小約4k,兼容性在ie6及以上 都兼容,在瀏覽器和服務(wù)器間來(lái)回傳遞,因此它得在服務(wù)器的環(huán)境下運(yùn)行,而且可以設(shè)定過(guò)期時(shí)間,默認(rèn)的過(guò)期時(shí)間是session會(huì)話結(jié)束。

localStorage的大小約5M,兼容性在ie7及以上都兼容,有瀏覽器就可以,不需要在服務(wù)器的環(huán)境下運(yùn)行, 會(huì)一直存在,除非手動(dòng)清除 。

答案大致分為2種

存在cookie中

存在localStorage中

token過(guò)期時(shí),前端該怎么處理?

1.第一種:跳回登陸頁(yè)面重新登陸

2.第二種:攔截401重新獲取token

class HttpClient {

? /**

? * Create a new instance of HttpClient.

? */

? constructor() {

? ? this.interceptors = {

? ? ? request: [],

? ? ? response: []

? ? };

? }

? /**

? * Sends a single request to server.

? *

? * @param {Object} options - Coming soon.

? */

? sendRequest(options) {

? ? let requestOptions = options;

? ? if (!requestOptions.header) {

? ? ? requestOptions.header = {};

? ? }

? ? // 重新設(shè)置 Accept 和 Content-Type

? ? requestOptions.header = Object.assign(

? ? ? {

? ? ? ? Accept: 'application/json, text/plain, */*',

? ? ? ? 'Content-Type': 'application/json;charset=utf-8'

? ? ? },

? ? ? requestOptions.header

? ? );

? ? this.interceptors.request.forEach((interceptor) => {

? ? ? const request = interceptor(requestOptions);

? ? ? requestOptions = request.options;

? ? });

? ? // 將以 Promise 返回?cái)?shù)據(jù), 無(wú) success、fail、complete 參數(shù)

? ? // let response = uni.request(requestOptions);

? ? // 使用Promise包裝一下, 以 complete方式來(lái)接收接口調(diào)用結(jié)果

? ? let response = new Promise((resolve, reject) => {

? ? ? requestOptions.complete = (res) => {

? ? ? ? const { statusCode } = res;

? ? ? ? const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304;

? ? ? ? if(statusCode==401){ //攔截401請(qǐng)求

? ? ? ? ? uni.reLaunch({ //關(guān)閉所有頁(yè)面直接跳到登陸頁(yè)面

? ? ? ? ? ? url: '/pages/login/login'

? ? ? ? ? });

? ? ? ? }

? ? ? ? if (isSuccess) {

? ? ? ? ? if(res.data.code==1){

? ? ? ? ? ? resolve(res.data);

? ? ? ? ? }else{

? ? ? ? ? ? reject(res);

? ? ? ? ? }

? ? ? ? } else {

? ? ? ? ? reject(res);

? ? ? ? }

? ? ? };

? ? ? requestOptions.requestId = new Date().getTime();

? ? ? uni.request(requestOptions);

? ? });

? ? this.interceptors.response.forEach((interceptor) => {

? ? ? response = interceptor(response);

? ? });

? ? return response;

? }

}

export default HttpClient;

這種方法適用余有登陸頁(yè)面的小程序,但同樣存在問(wèn)題,假如用戶在填寫表單,填寫完畢你卻告訴我重新登陸,確定用戶不回卸掉你的APP???

有人說(shuō)了? 異常退出 我會(huì)本地緩存填寫的表單內(nèi)容,當(dāng)然你要是能接受這種我也無(wú)話可說(shuō)!?。?/p>

我們要做的是無(wú)痛刷新toekn,那么首先y要在401攔截的時(shí)候去重新登陸獲取新的token

繼續(xù)優(yōu)化改造

import store from "../store/index.js";

class HttpClient {

? /**

? * Create a new instance of HttpClient.

? */

? constructor() {

? ? this.interceptors = {

? ? ? request: [],

? ? ? response: []

? ? };

? }

? /**

? * Sends a single request to server.

? *

? * @param {Object} options - Coming soon.

? */

? sendRequest(options) {

? ? let requestOptions = options;

? ? if (!requestOptions.header) {

? ? ? requestOptions.header = {};

? ? }

? ? // 重新設(shè)置 Accept 和 Content-Type

? ? requestOptions.header = Object.assign(

? ? ? {

? ? ? ? Accept: 'application/json, text/plain, */*',

? ? ? ? 'Content-Type': 'application/json;charset=utf-8'

? ? ? },

? ? ? requestOptions.header

? ? );

? ? this.interceptors.request.forEach((interceptor) => {

? ? ? const request = interceptor(requestOptions);

? ? ? requestOptions = request.options;

? ? });

? ? // 將以 Promise 返回?cái)?shù)據(jù), 無(wú) success、fail、complete 參數(shù)

? ? // let response = uni.request(requestOptions);

? ? // 使用Promise包裝一下, 以 complete方式來(lái)接收接口調(diào)用結(jié)果

? ? let response = new Promise((resolve, reject) => {

? ? ? requestOptions.complete = (res) => {

? ? ? ? const { statusCode } = res;

? ? ? ? const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304;

? ? ? ? if(statusCode==401){ //攔截401請(qǐng)求

? ? ? ? ? store

? ? ? ? ? ? .dispatch("auth/login")? ? //調(diào)用vueX中的登陸將登陸信息保存到VueX

? ? ? ? ? ? .then(()=>{ //提示用戶剛才操作無(wú)效重新操作一次

? ? ? ? ? ? ? uni.showToast({

? ? ? ? ? ? ? ? title: '請(qǐng)重新操作',

? ? ? ? ? ? ? ? duration: 2000,

? ? ? ? ? ? ? ? icon: "none",

? ? ? ? ? ? ? });

? ? ? ? ? ? })

? ? ? ? ? ? .catch(()=>{

? ? ? ? ? ? ? uni.showToast({

? ? ? ? ? ? ? ? title: '賬戶異常請(qǐng)重啟程序',

? ? ? ? ? ? ? ? duration: 2000,

? ? ? ? ? ? ? ? icon: "none",

? ? ? ? ? ? ? });

? ? ? ? ? ? })

? ? ? ? }

? ? ? ? if (isSuccess) {

? ? ? ? ? if(res.data.code==1){

? ? ? ? ? ? resolve(res.data);

? ? ? ? ? }else{

? ? ? ? ? ? reject(res);

? ? ? ? ? }

? ? ? ? } else {

? ? ? ? ? reject(res);

? ? ? ? }

? ? ? };

? ? ? requestOptions.requestId = new Date().getTime();

? ? ? uni.request(requestOptions);

? ? });

? ? this.interceptors.response.forEach((interceptor) => {

? ? ? response = interceptor(response);

? ? });

? ? return response;

? }

}

export default HttpClient;

到此我們實(shí)現(xiàn)的在401錯(cuò)誤時(shí)候去重新登陸獲取新的token,且告知用戶重新操作一次

到此你會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題,當(dāng)頁(yè)面存在一個(gè)請(qǐng)求,目前方案毫無(wú)問(wèn)題,但是當(dāng)存在兩個(gè)、三個(gè)、四個(gè)請(qǐng)求,你會(huì)罵娘

失敗3個(gè)請(qǐng)求會(huì)重新調(diào)用3次登陸會(huì)刷新3次token

那么此時(shí)要做的就是保證不多次登陸

思路加一個(gè)開(kāi)關(guān),當(dāng)在登陸過(guò)程中后續(xù)錯(cuò)誤不再走登陸接口

import store from "../store/index.js";

// 是否正在重新登陸刷新的標(biāo)記

var loginRefreshing = false

class HttpClient {

? /**

? * Create a new instance of HttpClient.

? */

? constructor() {

? ? this.interceptors = {

? ? ? request: [],

? ? ? response: []

? ? };

? }

? /**

? * Sends a single request to server.

? *

? * @param {Object} options - Coming soon.

? */

? sendRequest(options) {

? ? let requestOptions = options;

? ? if (!requestOptions.header) {

? ? ? requestOptions.header = {};

? ? }

? ? // 重新設(shè)置 Accept 和 Content-Type

? ? requestOptions.header = Object.assign(

? ? ? {

? ? ? ? Accept: 'application/json, text/plain, */*',

? ? ? ? 'Content-Type': 'application/json;charset=utf-8'

? ? ? },

? ? ? requestOptions.header

? ? );

? ? this.interceptors.request.forEach((interceptor) => {

? ? ? const request = interceptor(requestOptions);

? ? ? requestOptions = request.options;

? ? });

? ? // 將以 Promise 返回?cái)?shù)據(jù), 無(wú) success、fail、complete 參數(shù)

? ? // let response = uni.request(requestOptions);

? ? // 使用Promise包裝一下, 以 complete方式來(lái)接收接口調(diào)用結(jié)果

? ? let response = new Promise((resolve, reject) => {

? ? ? requestOptions.complete = (res) => {

? ? ? ? const { statusCode } = res;

? ? ? ? const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304;

? ? ? ? if(statusCode==401){ //攔截401請(qǐng)求

? ? ? ? ? if(!loginRefreshing){//防止重復(fù)登陸

? ? ? ? ? ? loginRefreshing = true

? ? ? ? ? ? store

? ? ? ? ? ? ? .dispatch("auth/login")? ? //調(diào)用vueX中的登陸將登陸信息保存到VueX

? ? ? ? ? ? ? .then(()=>{ //提示用戶剛才操作無(wú)效重新操作一次

? ? ? ? ? ? ? ? uni.showToast({

? ? ? ? ? ? ? ? ? title: '請(qǐng)重新操作',

? ? ? ? ? ? ? ? ? duration: 2000,

? ? ? ? ? ? ? ? ? icon: "none",

? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? })

? ? ? ? ? ? ? .catch(()=>{

? ? ? ? ? ? ? ? uni.showToast({

? ? ? ? ? ? ? ? ? title: '賬戶異常請(qǐng)重啟程序',

? ? ? ? ? ? ? ? ? duration: 2000,

? ? ? ? ? ? ? ? ? icon: "none",

? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? })

? ? ? ? ? ? ? .finally(()=>{

? ? ? ? ? ? ? ? //銷毀 是否正在重新登陸刷新的標(biāo)記

? ? ? ? ? ? ? ? loginRefreshing = false

? ? ? ? ? ? ? });

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? if (isSuccess) {

? ? ? ? ? if(res.data.code==1){

? ? ? ? ? ? resolve(res.data);

? ? ? ? ? }else{

? ? ? ? ? ? reject(res);

? ? ? ? ? }

? ? ? ? } else {

? ? ? ? ? reject(res);

? ? ? ? }

? ? ? };

? ? ? requestOptions.requestId = new Date().getTime();

? ? ? uni.request(requestOptions);

? ? });

? ? this.interceptors.response.forEach((interceptor) => {

? ? ? response = interceptor(response);

? ? });

? ? return response;

? }

}

export default HttpClient;

我們可以看到在遇到兩個(gè)401錯(cuò)誤時(shí)候并沒(méi)有請(qǐng)求兩次login,只請(qǐng)求一次,到此刷新token算是完成了,但是需要用戶配合去重新操作一次,還不是真正的無(wú)痛刷線token,做到用戶無(wú)感知

思路:將請(qǐng)求401的請(qǐng)求緩存起來(lái),在重新登陸完成之后再將緩存中的請(qǐng)求重新發(fā)出,

廢話不多說(shuō)直接上代碼

import AuthService from "@/services/auth.service";

import store from "../store/index.js";

// 是否正在重新登陸刷新的標(biāo)記

var loginRefreshing = false

// 重試隊(duì)列,每一項(xiàng)將是一個(gè)待執(zhí)行的函數(shù)形式

let requests = []

class HttpClient {

? /**

? * Create a new instance of HttpClient.

? */

? constructor() {

? ? this.interceptors = {

? ? ? request: [],

? ? ? response: []

? ? };

? }

? /**

? * Sends a single request to server.

? *

? * @param {Object} options - Coming soon.

? */

? sendRequest(options) {

? ? let requestOptions = options;

? ? if (!requestOptions.header) {

? ? ? requestOptions.header = {};

? ? }

? ? // 重新設(shè)置 Accept 和 Content-Type

? ? requestOptions.header = Object.assign(

? ? ? {

? ? ? ? Accept: 'application/json, text/plain, */*',

? ? ? ? 'Content-Type': 'application/json;charset=utf-8'

? ? ? },

? ? ? requestOptions.header

? ? );

? ? this.interceptors.request.forEach((interceptor) => {

? ? ? const request = interceptor(requestOptions);

? ? ? requestOptions = request.options;

? ? });

? ? // 將以 Promise 返回?cái)?shù)據(jù), 無(wú) success、fail、complete 參數(shù)

? ? // let response = uni.request(requestOptions);

? ? // 使用Promise包裝一下, 以 complete方式來(lái)接收接口調(diào)用結(jié)果

? ? let response = new Promise((resolve, reject) => {

let timeId = setTimeout(()=>{

? reject({statusCode:504});

},10000)

? ? ? requestOptions.complete = (res) => {

? ? ? ? clearTimeout(timeId)

? ? ? ? const { statusCode } = res;

? ? ? ? const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304;

? ? ? ? if(statusCode==401){ //無(wú)痛刷新token

? ? ? ? ? if(!loginRefreshing){//防止重復(fù)登陸

? ? ? ? ? ? loginRefreshing = true

? ? ? ? ? ? store.dispatch("auth/logout");

? ? ? ? ? ? store

? ? ? ? ? ? ? .dispatch("auth/login")

? ? ? ? ? ? ? .then(()=>{

? ? ? ? ? ? ? ? //所有存儲(chǔ)到對(duì)列組中的請(qǐng)求重新執(zhí)行。

? ? ? ? ? ? ? ? requests.forEach(callback=>{

? ? ? ? ? ? ? ? ? callback(AuthService.getToken() ? AuthService.getToken() : "")

? ? ? ? ? ? ? ? })

? ? ? ? ? ? ? ? //重試隊(duì)列清空

? ? ? ? ? ? ? ? requests = []

? ? ? ? ? ? ? })

? ? ? ? ? ? ? .catch(()=>{

? ? ? ? ? ? ? ? uni.showToast({

? ? ? ? ? ? ? ? ? title: '賬戶異常請(qǐng)重啟程序',

? ? ? ? ? ? ? ? ? duration: 2000,

? ? ? ? ? ? ? ? ? icon: "none",

? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? })

? ? ? ? ? ? ? .finally(()=>{

? ? ? ? ? ? ? ? //銷毀 是否正在重新登陸刷新的標(biāo)記

? ? ? ? ? ? ? ? loginRefreshing = false

? ? ? ? ? ? ? });

? ? ? ? ? }

? ? ? ? ? return new Promise((resolve) => {

? ? ? ? ? ? // 將resolve放進(jìn)隊(duì)列,用一個(gè)函數(shù)形式來(lái)保存,等token刷新后直接執(zhí)行

? ? ? ? ? ? requests.push((token) => {

? ? ? ? ? ? ? requestOptions.header.token = token //帶著登陸后的新token

? ? ? ? ? ? ? resolve(uni.request(requestOptions))

? ? ? ? ? ? })

? ? ? ? ? })

? ? ? ? }

? ? ? ? if (isSuccess) {

? ? ? ? ? if(res.data.code==1){

? ? ? ? ? ? resolve(res.data);

? ? ? ? ? }else{

? ? ? ? ? ? reject(res);

? ? ? ? ? }

? ? ? ? } else {

? ? ? ? ? reject(res);

? ? ? ? }

? ? ? };

? ? ? requestOptions.requestId = new Date().getTime();

? ? ? uni.request(requestOptions);

? ? });

? ? this.interceptors.response.forEach((interceptor) => {

? ? ? response = interceptor(response);

? ? });

? ? return response;

? }

}

export default HttpClient;

知識(shí)點(diǎn),在重新獲取新的token后要將緩存起來(lái)的請(qǐng)求中的token替換為重新登陸后新的token

到此無(wú)痛刷新token續(xù)接401請(qǐng)求的方法已經(jīng)處理完畢,在用戶提交表單時(shí)候遇到token失效重新獲取新的token再續(xù)接表單請(qǐng)求,此時(shí)用戶毫無(wú)感知,可能在請(qǐng)求時(shí)間上多了延遲,體驗(yàn)好感度+99,哈哈哈哈哈? ? ?

到此無(wú)痛刷新token續(xù)接401已經(jīng)完成請(qǐng)求快去試試吧

提示:有些后段在接口請(qǐng)求做了簽名,記得像更換token一樣在重新登陸完成之后更換新的時(shí)間戳新的簽名等字段

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容