Token expiration processing

Time:2021-12-4

The token is used for interface authentication, but the token has an expiration time set by the back end. When the token expires, data can no longer be requested
The expiration time set by the backend in the project is 24h. During the test, we can manually modify the token value to invalidate the token
Treatment method:

  • Method 1: the user can log in again and get a new token. However, when the expiration time is short, the user has to log in again every time, and the experience is very poor
    • In order to improve the information security of users, the expiration time of tokens is relatively short (even if they are leaked, they will expire and become invalid in a short time)
  • Method 2: automatically generate a new token for the user according to the user information to reduce the login times

If we observe the previous functions, there are three token related information in the response information of the interface

  • access_ Token: the currently used token, which is used to access the interface requiring authorization
  • expires_ in:access_ Expiration time of token
  • refresh_ Token: refresh to get new access_ token
    There are two ways to refresh Tokens:
    Method 1:
    Intercept before each request is initiated, according to expires_ In determines whether the token has expired. If it has expired, it will be refreshed before continuing to request the interface
    • Advantages: pre request interception processing can save the number of requests
    • Disadvantages: the backend needs to provide the token expiration time field (e.g. expires_in), and it needs to be judged in combination with the local time of the computer. If the computer time is tampered with (especially when it is full than the server time), the interception will fail
      Method 2:
      Intercept after each request response. If the request fails (caused by token expiration), refresh the token and then refresh the request interface
    • Advantages: no token expiration time field is required, and there is no need to judge the time
    • Disadvantages: consume one more request
      Method 2 is recommended here. In comparison, method 2 is more stable and will not cause unexpected problems

Axios response interceptor and error handling

Response interceptorAfter the response is received, it will be intercepted by the interceptor before the corresponding request is processed. The corresponding information is saved in the response interceptor parameter response

//Axios official document: response interceptor
// Add a response interceptor
axios.interceptors.response.use(function (response) {
  // Any status code that lie within the range of 2xx cause this function to trigger
  // Do something with response data
  return response;
}, function (error) {
  // Any status codes that falls outside the range of 2xx cause this function to trigger
  // Do something with response error
  return Promise.reject(error);
});

Then let’s set the response interceptor to utils / request.js and change Axios to the created request (because we use the eslint specification, remember to remove all semicolons)

  • Error requires console. Dir () output
// utils/request.js
...
//Set response interceptor
request.interceptors.response.use(function (response) {
  //If the status code is 2XX, it will enter here
  Console.log ('request response succeeded: ', response)
  return response
}, function (error) {
  //More than 2XX will enter here
  console.dir(error)
  return Promise.reject(error)
})
export default request

Axios error handling

error handling, you need to find a specific error condition in the interceptor to refresh the token
When an error occurs, set the prompt through elemnt’s message component. Here, we use the import method

  • The imported message is the same as this. $message used before, but the import method is different from the operation method
//The message component function of element is introduced through local introduction
import { Message } from 'element-ui'
//Response interceptor
request.interceptors.response.use(function (response) {
  //The status code 2XX will execute here
  Console.log ('response succeeded ', response)
  return response
}, function (error) {
  if (error.response) {
    //The request is sent successfully and the response is received, but the status code is failed
    //1. Judge the failed status code (mainly processing 401)
    const { status } = error.response
    let errorMessage = ''
    if (status === 400) {
      ErrorMessage = 'request parameter error'
    } else if (status === 401) {
      //2. Token invalid (expired) processing
      ErrorMessage = 'invalid token'
    } else if (status === 403) {
      ErrorMessage = 'no permission, please contact the administrator'
    } else if (status === 404) {
      ErrorMessage = 'requested resource does not exist'
    } else if (status >= 500) {
      ErrorMessage = 'server error, please contact administrator'
    }
    Message.error(errorMessage)
  } else if (error.request) {
    //The request was sent successfully and no response was received
    Message. Error ('request timeout, please try again ')
  } else {
    //Unexpected error
    Message.error(error.message)
  }
  //Continue to throw the error object of this request backward and let the processing function receiving the response operate
  return Promise.reject(error)
})

Refresh token

The HTTP status code 401 indicates that it is not authorized, and the conditions leading to 401 are:

  • No token
  • Invalid token
  • Token expired
    Judgment method:
    • Detect whether refresh exists_ Token: (the back end usually restricts each refresh_token to obtain a new token only once)
      • If so, use refresh_ Token to get new access_ token
        • After successful acquisition, restart to send the request and request the interface data
        • Failed to get, jump to login page
      • If not, go to the login page
        To jump, route / index.js is introduced into utils / request.js
// utils/request.js
//Introducing router
import router from '@/router'

First, check whether the store has user information (if yes, it proves that it is a normal login, and there must be a refresh_token). If there is, there must be a refresh_ If you use a token, you can request a new access_ The corresponding refresh is required for the tokenInterfaceNext, check whether there is a new access_ token

  • If it fails, clear the user information and jump to the login page
    • The jump login operation is the same as before, and it is recommended to encapsulate it
  • If successful, update access_ Token, and re request the previous 401 interface at the same time
// utils/
...
//Encapsulates the function to jump to the login page
function redirectLogin () {
  router.push({
    name: 'login',
    query: {
      //Route.currentroute is used to obtain the route information object corresponding to the current route
      redirect: router.currentRoute.fullPath
    }
  })
}

//Set response interceptor
request.interceptors.response.use(function (response) {
  ...
}, function (error) {
  //More than 2XX will enter here
  if (error.response) {
    ...
  } else if (status === 401) {
    if (!store.state.user) {
      /* router.push({
        name: 'login',
        query: {
          //Route.currentroute is used to obtain the route corresponding to the current route
          redirect: router.currentRoute.fullPath
        }
      }) */
      //Change to call after encapsulating the function
      redirectLogin()
      //Prevent subsequent operations and throw down the error object
      return Promise.reject(error)
    }
    ...
    }).then(res => {
      if (res.data.state !== 1) {
        //Clear invalid user information
        store.commit('setUser', null)
        //Jump to landing page
        /* router.push({
          name: 'login',
          query: {
            //Route.currentroute is used to obtain the route corresponding to the current route
            redirect: router.currentRoute.fullPath
          }
        }) */
        //Change to call after encapsulating the function
        redirectLogin()
        //Prevent subsequent operations and throw down the error object
        return Promise.reject(error)
      }
      ...
        }).catch(() => {
        store.commit('setUser', null)
        /* router.push({
          name: 'login',
          query: {
            //Route.currentroute is used to obtain the route corresponding to the current route
            redirect: router.currentRoute.fullPath
          }
        }) */
        //Change to call after encapsulating the function
        redirectLogin()
        return Promise.reject(error)
      })
  } else if (status === 403) {
  ...

Handle repeated refresh of token

If there are multiple requests in the page (there will not be only one request in most pages), if the token expires, each request will refresh the token. At this time, it is meaningless to refresh multiple times, which increases the number of requests, and additional problems will occur

Token expiration processing

When I ask for user information many times, I will return to the login page

According to the developer tool of the browser, there are two refresh token requests, due to the refresh carried by the two refresh tokens_ The same token will lead to one success and one failure, and the failure will lead to the page Jump to the request page

Token expiration processing

To avoid multiple requests to refresh the token, you can mark the refresh status of the token through a variable isrefreshing

  • The default status is false, and it is detected before sending the refresh token request. Only when the status is false can it be sent
  • When sending a refresh request, set the flag to true
  • The request is completed and set to false
// layout/components/app-header.vue
...
//Updating token
let isRefreshing = false

request.interceptors.response.use(function (response) {
...
  } else if (status === 401) {
    if (!store.state.user) {...}
    //Before sending a refresh request, judge whether there are other sent refresh requests in isrefreshing
    //1. If yes, suspend the current request and resend it after the token is refreshed. Set it to return here first
    if (isRefreshing) {
      return
    }
    //2. If not, update isrefreshing and send a request to continue the follow-up operation
    isRefreshing = true
    //Send refresh request
    return request({
     ...
    }).then(res => {
      ...
    }).catch(() => {
      ...
    }).finally(() => {
      //3. After the request is completed, set isrefreshing to false regardless of success or failure
      isRefreshing = false
    })
  } else if (status === 403) {
...

Although the problem of refreshing the token has been solved, only one of the two previous requests has been successfully executed, and the other requests have been blocked
How to solve it?
We declare an array to store all pending requests. When the token is refreshed, we will resend these requests

//Whether the store is updating the status of the token
let isRefreshing = false
//Store pending requests due to token refresh
let requests = []
//Response interceptor
request.interceptors.response.use(function (response) {
  //The status code 2XX will execute here
  Console.log ('response succeeded ', response)
  return response
}, function (error) {
  if (error.response) {
    //The request is sent successfully and the response is received, but the status code is failed
    //1. Judge the failed status code (mainly processing 401)
    const { status } = error.response
    let errorMessage = ''
    if (status === 400) {
      ErrorMessage = 'request parameter error'
    } else if (status === 401) {
      //2. Token invalid (expired) processing
      //First, there is no token information
      if (!store.state.user) {
        redirectLogin()
        return Promise.reject(error)
      }
      //Check whether there is a request to refresh the token
      if (isRefreshing) {
        //Save the current failed request and store it in the request list
        return requests.push(() => {
          //After the current function is called, this failed request will be sent automatically
          request(error.config)
        })
      }
      isRefreshing = true
      //Second, the token is invalid (wrong token, expired token)
      //Send a request to get a new access_ token
      return request({
        method: 'POST',
        url: '/front/user/refresh_token',
        data: qs.stringify({
          refreshtoken: store.state.user.refresh_token
        })
      }).then(res => {
        //- failed to refresh token
        if (res.data.state !== 1) {
          //Clear invalid user information
          store.commit('setUser', null)
          //Encapsulates duplicate jump login operations
          redirectLogin()
          return Promise.reject(error)
        }
        //Refresh token succeeded
        //Store new token
        store.commit('setUser', res.data.content)
        //Resend failed requests
        //According to requests
        //Send multiple failed requests
        requests.forEach(callback => callback())
        //Clear the requests after sending
        requests = []
        //Send this request
        return request(error.config)
      }).catch(err => {
        console.log(err)
      }).finally(() => {
        //It will be executed regardless of success or failure
        //After the request is sent, the response is processed, and the refresh status is changed to false
        isRefreshing = false
      })

solve

Recommended Today

Hive built-in function summary

1. Related help operation functions View built-in functions: Show functions; Display function details: desc function ABS; Display function extension information: desc function extended concat; 2. Learn the ultimate mental method of built-in function Step 1: carefully read all the functions of the show functions command to establish an overall understanding and impression Step 2: use […]