接上文 2 楼 Laf 云函数的鉴权,获取 token ,token过期处理
借用 OAuth2.0 access_token 和 refresh_token 的机制,升级下鉴权逻辑,让活跃用户保持不掉线的状态。
刚开始研究后端,各位海涵。如果有更好的方案,请赐教!
原理
登录成功后服务端生成俩 token 给客户端。客户端正常请求时使用 access_token,如果服务端返回登录超时的状态,则客户端拿着 refresh_token 去请求刷新接口,重新签发两个 token,最后客户端拿着新的 access_token 重新请求。
Laf 云函数
export async function main(ctx: FunctionContext) {
return { user: ctx.user }
}
import cloud from '@lafjs/cloud'
export async function main(ctx: FunctionContext) {
const uid = Math.round(Math.random() * 10)
const access_token = cloud.getToken({
uid,
message: '鉴权token',
exp: Math.floor(Date.now() / 1000) + 5
})
const refresh_token = cloud.getToken({
uid,
message: '刷新token',
exp: Math.floor(Date.now() / 1000) + 10
})
return { code: 20000, data: { access_token, refresh_token } }
}
import cloud from '@lafjs/cloud'
export default async function (ctx: FunctionContext) {
const tokenInfo = cloud.parseToken(ctx.body.refresh_token)
if (!tokenInfo) {
return {
code: 40101,
message: '刷新 token 已过期,请重新登录'
}
}
const uid = tokenInfo.uid
const access_token = cloud.getToken({
uid,
message: '鉴权token',
exp: Math.floor(Date.now() / 1000) + 5
})
const refresh_token = cloud.getToken({
uid,
message: '刷新token',
exp: Math.floor(Date.now() / 1000) + 10
})
return { code: 20000, data: { access_token, refresh_token } }
}
export async function main(ctx: FunctionContext) {
const whiteList = ['/refresh', '/login']
if (whiteList.includes(ctx.request.path)) {
return true
}
if (!ctx.user) {
ctx.response.send({ code: 40100, message: 'token 过期,请刷新 token' })
return false
}
return true
}
最后,是前端实现,基于 axios 拦截器。
import axios from 'axios';
const instance = axios.create({
baseURL: import.meta.env.VITE_URL
})
let pending = []
let isPending = false
const refreshToken = () => {
console.log('请求刷新。。。')
return instance.post('/refresh', {
refresh_token: localStorage.getItem('refresh_token')
})
}
instance.interceptors.request.use(
(config) => {
if (config.needToken) {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response) => {
const { code } = response.data
if (code === 40100) {
const originalConfig = response.config;
if (isPending) {
console.log('已经有刷新请求了,先加到队列等着吧。。。')
return new Promise((resolve) => {
pending.push((token) => {
originalConfig.headers['Authorization'] = token
console.log('执行队列。。。')
resolve(instance(originalConfig))
})
})
} else {
console.log('请求40100了,开始刷新token')
isPending = true
return refreshToken()
.then((response) => {
console.log('刷新token返回内容', response)
if (response.data.code === 20000) {
localStorage.setItem('token', response.data.data.access_token)
localStorage.setItem('refresh_token', response.data.data.refresh_token)
pending.forEach((cb) => cb(response.data.data.refresh_token))
pending = []
originalConfig.headers['Authorization'] = response.data.data.refresh_token
return instance(originalConfig)
} else if (response.data.code === 40101) {
console.log('刷新token也过期了,重新登录吧!')
return response.data
}
})
.catch((error) => {
console.error(error)
})
.finally(() => {
isPending = false
})
}
} else {
return response;
}
},
async (error) => {
return Promise.reject(error);
}
);