双Token实现无感刷新

360影视 欧美动漫 2025-08-14 12:08 1

摘要:想象一下你正在刷视频,突然提示"登录已过期,请重新登录",需要退出当前页面重新输入密码。这样的体验非常糟糕!无感刷新就是为了解决这个问题:让用户在不知不觉中完成身份续期,保持长时间在线状态。

想象一下你正在刷视频,突然提示"登录已过期,请重新登录",需要退出当前页面重新输入密码。这样的体验非常糟糕!无感刷新就是为了解决这个问题:让用户在不知不觉中完成身份续期,保持长时间在线状态。

我们使用两个令牌:

短令牌:access_token (1小时):用于日常请求长令牌:refresh_token (7天):专门用来刷新令牌

工作流程:

用户登录 → 获取双令牌 → access_token 过期 → #技术分享 用 refresh_token 获取新的双令牌 → 自动续期const login = async => { const res = await userLogin(user); localStorage.setItem('access_token', res.access_token); localStorage.setItem('refresh_token', res.refresh_token);}

通过请求拦截器自动添加认证头:

api.interceptors.request.use(config => { const access_token = localStorage.getItem('access_token'); if (access_token) { config.headers.Authorization = `Bearer ${access_token}`; } return config;})

响应拦截器发现401登录过期的错误时自动请求刷新

验证长令牌是否失效

api.interceptors.response.use( (response) => { return response }, async (error) => { const { data, status, config } = error.response; if (status === 401 && config.url !== '/refresh') { const res = await refreshToken if (res.status === 200) { return api(config) } else { window.location.href = '/login' } } })const access_token = generateToken(user, '1h');const refresh_token = generateToken(user, '7d');app.get('/refresh', (req, res) => { const oldRefreshToken = req.query.token; try { const userData = verifyToken(oldRefreshToken); const newAccessToken = generateToken(userData, '1h'); const newRefreshToken = generateToken(userData, '7d'); res.json({ access_token: newAccessToken, refresh_token: newRefreshToken }); } catch (error) { res.status(401).send('令牌已失效'); }})登录

登录成功

欢迎回来,{{ username }}

您的邮箱:{{ email }}

获取首页数据import { ref } from 'vue' import { userLogin, getHomeDataApi } from './api.js'const isLogin = ref(false) const username = ref('') const email = ref('') const password = ref('')const login = async => { username.value = 'zs' email.value = '123@qq.com' password.value = '123'const res = await userLogin({username: username.value, email: email.value, password: password.value}) console.log(res) const {access_token, refresh_token, userInfo} = res.data if (access_token) { isLogin.value = true } localStorage.setItem('access_token', access_token) localStorage.setItem('refresh_token', refresh_token) }const getHomeData = async => { const res = await getHomeDataApi console.log(res) }import axios from 'axios'const api = axios.create({ baseURL: 'http://localhost:3000', timeout: 3000, })api.interceptors.request.use(config => { const access_token = localStorage.getItem('access_token'); if (access_token) { config.headers.Authorization = `Bearer ${access_token}`; } return config; })api.interceptors.response.use( (response) => { return response }, async (error) => { const { data, status, config } = error.response; if (status === 401 && config.url !== '/refresh') { const res = await refreshToken if (res.status === 200) { return api(config) } else { window.location.href = '/login' } } } )export const userLogin = (data) => { return api.post('/login', data) }export const getHomeDataApi = => { return api.get('/home') }async function refreshToken { const res = await api.get('/refresh', { params: { token: localStorage.getItem('refresh_token') } }) localStorage.setItem('access_token', res.data.access_token) localStorage.setItem('refresh_token', res.data.refresh_token) return res }server.jsconst express = require('express');const app = express;const port = 3000;app.use(express.json);const jwtToken = require('./token.js');const cors = require('cors');app.use(cors)const users = [ { username: 'zs', password: '123', email: '123@qq.com' }, { username: 'ls', password: '456', email: '456@qq.com' } ]app.get('/', (req, res) => { res.send('Hello World!'); });app.post('/login', (req, res) => { const { username, password } = req.body; const user = users.find(user => user.username === username); if (!user) { return res.status(404).json({status: 'error', message: '用户不存在'}); } if (user.password !== password) { return res.status(401).json({status: 'error', message: '密码错误'}); }const access_token = jwtToken.generateToken(user, '1h'); const refresh_token = jwtToken.generateToken(user, '7d');res.json({ userInfo: { username: user.username, email: user.email }, access_token, refresh_token })})app.get('/home', (req, res) => { const authorization = req.headers.authorization; if (!authorization) { return res.status(401).json({status: 'error', message: '未登录'}); }try { const token = authorization.split(' ')[1]; const data = jwtToken.verifyToken(token); res.json({ status: 'success', message: '验证成功', data: data }); } catch (error) { return res.status(401).json({status: error, message: 'token 失效,请重新登录'}); }})app.get('/refresh', (req, res) => { const { token } = req.query;try { const data = jwtToken.verifyToken(token); const access_token = jwtToken.generateToken(data, '1h'); const refresh_token = jwtToken.generateToken(data, '7d'); res.json({ status: 'success', message: '刷新成功', access_token, refresh_token }); } catch (error) { return res.status(401).json({status: error, message: 'token 失效,请重新登录'}); } })app.listen(port, => { console.log(`Example app listening on port ${port}`); })const jwt = require('jsonwebtoken');function generateToken(user, expiresIn) { const payload = { username: user.username, email: user.email }; const secret = 'my_secret_key'; const options = { expiresIn: expiresIn }; return jwt.sign(payload, secret, options); }function verifyToken(token) { const secret = 'my_secret_key'; const decoded = jwt.verify(token, secret); return decoded; }module.exports = { generateToken, verifyToken };用户发起请求 → 携带access_token → 服务端验证 ↓ 无效/过期触发401错误 → 前端拦截 → 发起refresh_token刷新请求 ↓ 刷新成功更新本地令牌 → 重新发送原请求 → 用户无感知 ↓ 刷新失败跳转登录页面 → 需要重新认证

来源:墨码行者

相关推荐