前端如何无感自动刷新token?

看了很多token自动刷新文章,还是想笼统问下...

最近接到一个新需求,就是登录后进入到大屏,大屏是需要一直挂着展示,不要让它频繁掉线重新登录,方案是让前端实现token定时自动刷新,防止频繁掉线重新登录,但是目前后端返回的只有token值,没有返回过期时间,也没有提供给刷新token的接口,这种情况下前端如何实现自动刷新?需要后端给我提供什么or如何处理?

阅读 7.4k
7 个回答

最好的方法就是去跪求后端大哥,提供一个RefreshToken机制和接口,并约定一个token的固定有效期(比如两个小时)或者增加字段过期时间/有效期限,然后前端根据需要可以在token快过期的时候用refreshToken请求一个新的token值,后端保证新旧token值都同时有效,旧tokn过期时间过了旧token即刻失效,完全切换到新token。

如果后端不好大改,只有做一个请求进度框了,做不到无感,只能是定时刷新token,请求的时候如果发现token刚好失效,重新获取token,并显示请求进度框,最多就是请求进度状态的组件显示在数据显示区域,可以不弹窗那样突兀。

RefreshToken 可以多自己搜索理解下,知乎,掘金都可以去搜搜看。

没有刷新token的接口,有获取token的接口么。
有的话定时去调用这个接口主动更新token即可。
没有的话是后端把token写入cookie的么,那这个就得和后端联合开发了。

需要后端返回token和refreshToken,当token过期的时候,在响应拦截器中判断,请求刷新token的接口,携带之前的refreshToken,换回新的token和refreshToken并存储到状态管理库或缓存。
需要注意的是,需要记录失败的请求,后续重新获取新的token后,重新发起请求。
要避免多次请求触发响应拦截器对于token过期的处理逻辑,可以加一个状态变量(isRefresh)来控制,防止多次触发导致token一直处于失效的状态。

大屏的数据,应该是定时刷新的吧,不然统计数据改变,还有重新手动刷新一下数据吗,
有定时刷新,就不用担心掉线,刷新的时候更新一下token,或后端有处理,前端不用处理,就不会掉线。

如果需要实现无感刷新,一般会生成两个token。

token:用于用户正常验证。

refresh_token:更新(生成新)token时,需要验证refresh_token是否过期或被篡改。

当验证token时,发现token过期时返回自定义状态码

前端使用响应拦截器,根据自定义的状态码进行拦截。

使用refresh_token来重新生成token然后重发请求

需要后端配合设计一个token 刷新接口

后端实现


const express = require('express')
const jwt = require('jsonwebtoken');
const app = express();
app.get('/login', (req, res) => {
    const { name, password } = req
    //登陆的时候用登录的账号和密码生成 refresh_token  过期时间 12 小时
    const refresh_token = jwt.sign({ name: "name", password: "123456" }, 'token', {
        { expiresIn: "12h" }

    })
// 用生成的 refresh_token 去生成 token 返回到前端  过期时间 15 分钟
const token = jwt.sign({ refresh_token: refresh_token }, 'token', {
    expiresIn: "15m"
})

res.status(200).send({ refresh_token, token })
})

app.get('/refresh_token', (req, res) => {
    const { data } = req
    jwt.verify(data.refresh_token, "bad secret", (error, decoded) => {
        if (error) {
            console.log("refresh_token 也过期了,需要重新登陆")
            return
        }
         // refresh_token 没有过期, 刷新 token返回前端
        const token = jwt.sign({ refresh_token: refresh_token }, 'token', {
            expiresIn: "15m"
        })
        res.status(200).send({ token })
    })

})

app.listen(3000); 

前端实现

import axios from 'axios'
import store from '@/store'
import {
  httpUrlbase
} from '@/utils/api.js'
import {
  Message
} from 'element-ui'
 
// 创建axios实例
const service = axios.create({
  baseURL: httpUrlbase, // url = base url + request url
  timeout: 120000 // 请求超时设置
})
service.defaults.headers = {
  'Content-Type': 'application/json;charset=UTF-8'
}
 
// 请求拦截器
service.interceptors.request.use(
  config => {
    //给请求头添加token
    if (store.getters.token) {
      config.headers['Token'] = store.getters.token
    }
    //给请求参数去除前后空格
    if (config.method.toLowerCase() == 'post') {
      for (let key in config.data) {
        config.data[key] = typeof config.data[key] == 'string' ? config.data[key].trim() : config.data[key]
      }
    } else {
      for (let key in config.params) {
        config.params[key] = typeof config.params[key] == 'string' ? config.params[key].trim() : config.params[key]
      }
    }
    return config
  },
  error => {
    // 处理请求错误
    return Promise.reject(error)
  }
)
 
// 响应拦截
service.interceptors.response.use(
  response => {
    //从响应体里面结构出config(请求体)data(响应数据别名为res)
    const {
      config,
      data: res,
    } = response
 
    // 如果自定义代码不是'10000',则判断为错误。
    if (res.code && res.code + '' !== '10000') {
      //code 40007 sub_code以'token-is-expired'结束code为业务状态码sub_code为具体错误状态码
      if (res.code + '' == '40007' && (res.sub_code.endsWith('token-is-expired'))) {
        return new Promise(function (resolve, reject) {
          //post请求放到request body响应的时候是json字符串所以要转为json对象
          if (config.method.toLowerCase() == 'post') {
            config.data = JSON.parse(config.data)
          }
          //获取新的token
          const verificationResult = await service({
            url: "/api/v1/refresh_token",
            method: "POST",
            data: {
              refresh_token:store.getters.refresh_token
            }
          })
          if (verificationResult && verificationResult.code == "10000") {
            store.commit("user/SET_TOKEN", verificationResult.data.token)//存储token
            const newRes = await service(config) //重发请求
            resolve(newRes);//将响应结果返回到业务层
          }
        })
      } else {
        Message(res.sub_message);//统一错误提示
      }
    } else {
      return res
    }
  },
  error => {
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)
 
export default service

大概就是这么一个流程,在网上总结的一些代码,你做一个参考吧

找个不起眼的接口定时调吧,后台应该是做了延时的;

你们这个token过时刷新页面是怎么个实现方式?在点击新链接后,后台检查token过期,把url拦截了?
token 是前后台通信数据,你肯定需要后台协助的。

后台可以提供一个心跳链接接口,前端定时发送心跳请求,确保前端存活,让后台刷新token过期时间。
又或者如楼上所说,让后台提供获取新token的接口,前端定时获取新token,刷新cookie中的token。
又或者,你在cookie中保存了用户名密码,用异步方式调用后台登录接口,重新登录获取新的token。这个不太好,除非后台实在不想配合,非得你自己完成这个功能...

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题