简体   繁体   English

如何在客户端(vue.js)实现自动刷新?

[英]How to implement auto refresh in client side(vue.js)?

Note: I have seperated my client(Vue.js) and server(DjangoRest).注意:我已经分离了我的客户端(Vue.js)和服务器(DjangoRest)。 I'm using JWT to validate every request made from the client to the server.我正在使用 JWT 来验证从客户端到服务器的每个请求。 Flow- Client sends user credentials to server. Flow-Client 将用户凭据发送到服务器。 Server sends back a refresh and access token if credentials are valid.如果凭据有效,服务器会发回刷新和访问令牌。 Client stores the access and refresh token.客户端存储访问和刷新令牌。 I have set the refresh token expiry to 1 week,access to 30 mins.我已将刷新令牌有效期设置为 1 周,访问时间为 30 分钟。 Next, I want to make sure that the access token is auto refreshed 15 mins prior to its expiry.接下来,我想确保访问令牌在到期前 15 分钟自动刷新。 To do this, the stored refresh token in client side is send to the server, the server then issues a new access token and refresh token, sends it back to the client.为此,将客户端存储的刷新令牌发送到服务器,然后服务器发出新的访问令牌和刷新令牌,并将其发送回客户端。 How do i implement this in the Vuex store?.我如何在 Vuex 商店中实现它? I'm a complete newbie to web development and vue.js.我是网络开发和 vue.js 的新手。 It would be great if someone could provide some code or explain in details.如果有人可以提供一些代码或详细解释,那就太好了。

I have already implemented loginUser,logout user,registerUser in store and they are working fine.我已经在商店中实施了 loginUser、logout user、registerUser,它们运行良好。 But I'm stuck with the auto refresh logic.但我坚持使用自动刷新逻辑。 My guess is that the client has to repeatedly check the access token expiry time left.我的猜测是客户端必须反复检查剩余的访问令牌到期时间。 When about 15 mins is left, we have to initialize the autorefresh function.当还剩大约 15 分钟时,我们必须初始化自动刷新功能。 Please help me with this logic.请帮我解决这个逻辑。

Here's my Vueex store:这是我的 Vueex 商店:

import Vue from 'vue'
import Vuex from 'vuex'
import axiosBase from './api/axios-base'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
     accessToken: '' || null,
     refreshToken: '' || null
  },
  getters: {
    loggedIn (state) {
      return state.accessToken != null
    }
  },
  mutations: {
    loginUser (state) {
      state.accessToken = localStorage.getItem('access_token')
      state.refreshToken = localStorage.getItem('refresh_token')
    },
    destroyToken (state) {
      state.accessToken = null
      state.refreshToken = null
    }
  },
  actions: {
    registerUser (context, data) {
      return new Promise((resolve, reject) => {
        this.axios.post('/register', {
          name: data.name,
          email: data.email,
          username: data.username,
          password: data.password,
          confirm: data.confirm
        })
          .then(response => {
            resolve(response)
          })
          .catch(error => {
            reject(error)
          })
      })
    },
    // fetch data from api whenever required.
    backendAPI (context, data) {

    },
    logoutUser (context) {
      if (context.getters.loggedIn) {
        return new Promise((resolve, reject) => {
          axiosBase.post('/api/token/logout/')
            .then(response => {
              localStorage.removeItem('access_token')
              localStorage.removeItem('refresh_token')
              context.commit('destroyToken')
            })
            .catch(error => {
              context.commit('destroyToken')
              resolve()
            })
        })
      }
    },
    autoRefresh (context, credentials) {

    },
    loginUser (context, credentials) {
      return new Promise((resolve, reject) => {
        axiosBase.post('/api/token/', {
          username: credentials.username,
          password: credentials.password
        })
          .then(response => {
            localStorage.setItem('access_token', response.data.access)
            localStorage.setItem('refresh_token', response.data.refresh)
            context.commit('loginUser')
            resolve(response)
          })
          .catch(error => {
            console.log(error)
            reject(error)
          })
      })
    }
  }
})

Thank you in advance.先感谢您。

This is very much an idea question as you've pointed out and as such, there are many ways of solving it.正如您所指出的,这在很大程度上是一个想法问题,因此,有很多方法可以解决它。

One thing I try to keep in mind when dealing with such mechanisms is to always avoid polling when possible .在处理此类机制时,我要牢记的一件事是尽可能避免轮询 Here's a solution inspired by that design principle.这是受该设计原则启发的解决方案。

JWT tokens are valid for a very specific amount of time. JWT 令牌在非常特定的时间内有效。 The time left for expiration is readily available as part of the access token.剩余到期时间作为访问令牌的一部分很容易获得。 You can use a library such as jwt-decode to decode the access token and extract the expiration time.您可以使用诸如jwt-decode之类的库来解码访问令牌并提取过期时间。 Once you have the expiration time, you have a several options available:一旦你有到期时间,你有几个可用的选项:

  • Check token every time before making a request to know if it needs to be refreshed每次发起请求前检查token是否需要刷新
  • Use setTimeout to refresh it periodically X seconds before it expires使用setTimeout在到期前 X 秒定期刷新它

Your code could be implemented as follows:您的代码可以按如下方式实现:
Note : Please treat the following as pseudo-code.注意:请将以下内容视为伪代码。 I have not tested it for errors---syntax or otherwise.我没有测试它的错误——语法或其他方面。

export default new Vuex.Store({
  ...
  actions: {
    refreshTokens (context, credentials) {
      // Do whatever you need to do to exchange refresh token for access token
      ...
      // Finally, call autoRefresh to set up the new timeout
      dispatch('autoRefresh', credentials)
    },
    autoRefresh (context, credentials) {
      const { state, commit, dispatch } = context
      const { accessToken } = state
      const { exp } = jwt_decode(accessToken)
      const now = Date.now() / 1000 // exp is represented in seconds since epoch
      let timeUntilRefresh = exp - now
      timeUntilRefresh -= (15 * 60) // Refresh 15 minutes before it expires
      const refreshTask = setTimeout(() => dispatch('refreshTokens', credentials), timeUntilRefresh * 1000)
      commit('refreshTask', refreshTask) // In case you want to cancel this task on logout
    }
  }
})

It's better to rely on your server response code than expiration time.依赖你的服务器响应代码比过期时间更好。 Try to access the protected route, if it returns 401, ask for a new access token and then try again.尝试访问受保护的路由,如果返回 401,请请求新的访问令牌,然后重试。 If your refresh route also returns 401, make your user log in again.如果您的刷新路由也返回 401,请让您的用户重新登录。

What you need is an axios interceptor.你需要的是一个 axios 拦截器。

I implemented this one for a project我为一个项目实现了这个

import axios from 'axios'
import router from './router'
import store from './store'
import Promise from 'es6-promise'

const ax = axios.create({
  baseURL: 'http://localhost:3000',
  headers: {
    'Content-type': 'application/json'
  }
})

ax.interceptors.response.use(
  (response) => {
    return response
  },
  (err) => {
    console.log(err.response.config.url)
    // return other errors
    if (err.response.status !== 401) {
      return new Promise((resolve, reject) => {
        reject(err)
      })
    }
    // error on login
    if (err.response.config.url === '/auth/login') {
      return new Promise((resolve, reject) => {
        reject(err)
      })
    }
    // error on refresh
    if (err.response.config.url === '/auth/refresh') {
      console.log('ERRO NO REFRESH')
      router.push('/logout')
      return new Promise((resolve, reject) => {
        reject(err)
      })
    }
    // refresh
    return ax.get('/auth/refresh', { withCredentials: true }).then(
      success => {
        const config = err.response.config
        config.headers.Authorization = 'Bearer ' + success.data.access_token
        store.commit('setToken', success.data.access_token)
        return ax(config)
      }
    )
  }
)

export default ax

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM