401错误的场景
有如下两种情况会出现401错误:
- 未登陆用户做一些需要权限才能做的操作,代码会报出401错误。这种情况下,应该让用户回到登陆页。
- 登录用户的token过期了
整体目标是:通过axios响应拦截器来处理401问题。
理解token过期
登陆成功之后,接口会返回一个token值,这个值在后续请求时通过请求头时带上(就像是开门钥匙)。但是,这个值一般会有有效期(具体是多长,是由后端决定),假如在我这里有效期是2小时。
如果你上午8点登陆成功,到了10:01分,则token就会失效,再去发请求时,就会报401错误。
refresh_token和token的作用
当用户登陆成功之后,返回的token中有两个值,说明如下:
- token:
- 作用:在访问一些接口时,需要传入token,就是它。
- 有效期:2小时(安全)。
- refresh_token
- 作用: 当token的有效期过了之后,可以使用它去请求一个特殊接口(这个接口也是后端指定的,明确需要传入refresh_token),并返回一个新的token回来(有效期还是2小时),以替换过期的那个token。
- 有效期:14天。(最理想的情况下,一次登陆可以持续14天。)
响应拦截器功能
axios中提供了响应拦截器功能:所有从后端回来的响应都会集中进入响应拦截器中。所以,我们可以在响应拦截器中去写代码来统一解决。
request的响应拦截器中:
- 对于某次请求A,如果是401错误 (2)
- 有refresh_token,用refresh_token去请求回新的token (3)
- 新token请求成功 (4)
- 更新本地token (5)
- 再发一次请求A (6)
- 新token请求失败
- 携带请求地址,跳转到登陆页
- 没有refresh_token,说明没有登陆
- 携带请求地址,跳转到登陆页
封装独立的history
src/utils/history
import { createBrowserHistory } from 'history'
const history = createBrowserHistory()
export default history
App.tsx
import history from '@/utils/history'
function App () {
return (
<div className="App">
<Router history={history}>
处理401问题,token无感知刷新
request.ts
// 添加响应拦截器
instance.interceptors.response.use(
function (response) {
// 对响应数据做点什么
return response
},
async function (error: AxiosError<{ message: string }>) {
// 对响应错误做点什么
// console.dir(error)
// 1. 处理网络异常状况
// 如果请求延迟,response为undefined
if (!error.response) {
Toast.show('网络异常')
return Promise.reject(error)
}
// 2. 返回401(token过期)处理
if (error.response.status !== 401) {
Toast.show('操作异常')
return Promise.reject(error)
}
if (error.response.status === 401) {
// 2.1 获取refresh_token, 发送请求,获取最新的token
const { refresh_token } = getToken() // 从本地中获取refresh_token
if (refresh_token) {
try {
// 发请求重新获取token
const res = await axios.put(baseURL + 'authorizations', null, {
headers: {
Authorization: `Bearer ${refresh_token}`
}
})
// 2.2 保存到redux
store.dispatch(
savaToken({ token: res.data.data.token, refresh_token })
)
// 2.3 这个时候已经有token了,重新发请求
return instance(error.config)
} catch (error) {
// 如果使用refresh_token获取token的请求都失败了,就直接跳转到login
history.replace('/login', { from: location.pathname })
// 清空redux中的所有token
store.dispatch(savaToken({ token: '', refresh_token: '' }))
// 将错误抛出
return Promise.reject(error)
}
} else {
// 2.4 没有refresh_token 跳转到login,并携带当前页面的地址,支持回跳
history.replace('/login', { from: location.pathname })
}
// 2.5 清除 token 和 refresh_token 相关的数据
store.dispatch(savaToken({ token: '', refresh_token: '' }))
// 2.6 抛出错误
return Promise.reject(error)
}
return Promise.reject(error)
}
)