After the session expires, the token is refreshed and the interface is re requested (subscription publishing mode)

 

demand

In a page, when the request fails and 302 is returned, judge whether the interface expires or the login expires. If the interface expires, request a new token, and then take a new token to initiate the request again

 

thinking

  • At first, I thought of a black Technology (to be lazy), that is, after I got the new token, I directly forced the page to refresh, so that the interface in the page would refresh automatically ~ (convenience is convenience, but the user experience is not good)
  • At present, it is thought that when re requesting the interface, you can cooperate with the subscription and publication mode to improve the user experience

Response interception

First, we initiate a request Axios ({URL: '/ test', data: XXX}). Then (RES = > {})

After intercepting 302, we enter the refresh token logic

Response interception code

axios.interceptors.response.use(
    function (response) { 
        if (response.status == 200) { 
            return response;
        }
    },
    (err) => {
        //Refresh token
        let res = err.response || {}; 
        if (res.data.meta?.statusCode == 302) {
            return refeshToken(res);
        } else {  
            return err;
        }
    }
);
Copy code

The data format of our background is to judge the expiration according to the statusCode (you can judge according to your actual situation), and then enter the refrshToken method~

Refresh token method

//Avoid simultaneous requests from other interfaces (only one token interface is requested)
let isRefreshToken = false;
const refeshToken = (response) => {
   if (!isRefreshToken) {
            isRefreshToken = true;
            axios({
                //Get new token interface
                url: `/api/refreshToken`,
            })
                .then((res) => {
                    const { data = '', meta = {} } = res.data;
                    if (meta.statusCode === 200) {
                        isRefreshToken = false; 
                        //Release news
                        retryOldRequest.trigger(data);
                    } else { 
                        history.push('/user/login');
                    }
                })
                .catch((err) => { 
                    history.push('/user/login');
                });
        }
        //Collect subscribers and return the successful data to the original interface
        return retryOldRequest.listen(response);
};
Copy code

Seeing this, some friends are a little strange. What is retryOldRequest? Yes, this is our second male subscription publishing mode queue.

Subscription publishing mode

Take the failed interface as the subscriber and successfully get the new token before publishing (re requesting the interface).

The following is the code of subscription publication mode

const retryOldRequest = {
    //Maintain the response of the failed request
    requestQuery: [],

    //Add subscriber
    listen(response) {
        return new Promise((resolve) => {
            this.requestQuery.push((newToken) => { 
                let config = response.config || {};
                //Authorization is the identity token passed to the background
                config.headers['Authorization'] = newToken;
                resolve(axios(config));
            });
        });
    },

    //Release news
    trigger(newToken) {
        this.requestQuery.forEach((fn) => {
            fn(newToken);
        });
        this.requestQuery = [];
    },
};
Copy code

You don't need to pay attention to the logic of the subscriber first. You just need to know that the subscriber is the interface (reponse) after each request failure.

Every time we enter the refeshToken method, our failed interface will trigger retryOldRequest.listen to subscribe, and our requestQuery is the queue to save these subscribers.

Note: our subscriber queue requestQuery is the way to save the information to be published. After successfully obtaining the new token, retryOldRequest.trigger will publish these messages (New tokens) to the subscriber (the method to trigger the subscription queue).

The subscriber (response) has config configuration. After we get the new token (after publishing), we modify the request header autorization in config. With Promise, we can better get the interface data requested by the new token. Once the data is requested, we can return it to the original interface / test intact (because we returned the refreshToken in response to the interception, and the refreshToken returned the data returned by the subscriber retryOldRequest.listen, while the Lister returned the Promise data, and the Promise resolve d after the successful request).

Seeing this, do you feel a little windy~

In real development, our logic also includes login expiration (distinguished from request expiration). We judge whether the request expires or the login expires according to the current time - past time < expirstime (epirstime: the effective time returned after login). The following is the complete logic

The following is the complete code

const retryOldRequest = {
    //Maintain the response of the failed request
    requestQuery: [],

    //Add subscriber
    listen(response) {
        return new Promise((resolve) => {
            this.requestQuery.push((newToken) => { 
                let config = response.config || {};
                config.headers['Authorization'] = newToken;
                resolve(axios(config));
            });
        });
    },

    //Release news
    trigger(newToken) {
        this.requestQuery.forEach((fn) => {
            fn(newToken);
        });
        this.requestQuery = [];
    },
};
/**
 * sessionExpiredTips
 * Session Expiration:
 * Failed to refresh token. You have to log in again
 * The user is not authorized, and the page jumps to the login page 
 * Interface expiration = > refresh token
 * Login expired = > login again
 * expiresTime => 18000ms == 5h returned in this service
 * ****/

//Avoid simultaneous requests from other interfaces
let isRefreshToken = false;
let timer = null;
const refeshToken = (response) => {
    //Validity period after login
    let userExpir = localStorage.getItem('expiresTime');
    //current time 
    let nowTime = Math.floor(new Date().getTime() / 1000);
    //Last requested time
    let lastResTime = localStorage.getItem('lastResponseTime') || nowTime;
    //Save to local token after login
    let token = localStorage.getItem('token');

    if (token && nowTime - lastResTime < userExpir) {
        if (!isRefreshToken) {
            isRefreshToken = true;
            axios({
                url: `/api/refreshToken`,
            })
                .then((res) => {
                    const { data = '', meta = {} } = res.data;
                    isRefreshToken = false;
                    if (meta.statusCode === 200) {
                        localStorage.setItem('token', data);
                        localStorage.setItem('lastResponseTime', Math.floor(new Date().getTime() / 1000)
                        );
                        //Release news
                        retryOldRequest.trigger(data);
                    } else {
                       //Go login
                    }
                })
                .catch((err) => {
                    isRefreshToken = false;
                   //Go login
                });
        }
        //Collect subscribers and return the successful data to the original interface
        return retryOldRequest.listen(response);
    } else {
        //Throttling: avoid repeated operation
       //Go login
    }
};

// http response response interception
axios.interceptors.response.use(
    function (response) { 
        if (response.status == 200) {
            //Record the last operation time
           localStorage.setItem('lastResponseTime', Math.floor(new Date().getTime() / 1000));
            return response;
        }
    },
    (err) => { 
        let res = err.response || {}; 
        if (res.data.meta?.statusCode == 302) {
            return refeshToken(res);
        } else {
            // Error not reported by 302; 
            return err;
        }
    }
);

Copy code

The above is our business. If it's not well written, please bear it more~~

last

If you think this article is a little helpful to you, please give me a compliment. Or you can join my development exchange group: 1025263163 learn from each other, and we will have professional technical Q & A to solve your doubts

If you think this article is useful to you, please click star: http://github.crmeb.net/u/defu esteem it a favor  !

Keywords: github .NET crmeb

Added by irishpeck on Thu, 09 Dec 2021 02:42:06 +0200