最近发现用户认证和权限管理在很多含有登入的应用都要使用,但是每次都要进行重写Token验证和请求。我们需要实现一个开箱即用的解决方案,其中包括用户登入、Token 验证和请求封装等功能。在基于 Vue3.0、Vite、Ant-Design-Vue 和 TypeScript 的后台解决方案中,
1、登入验证 Token
在用户登入成功后,后端通常会返回一个 Token,前端需要将这个 Token 存储起来,并在后续的请求中携带这个 Token 以验证用户身份。为了实现这一功能,我们需要对请求进行封装,并在请求拦截器中注入 Token。
1.1 封装 Token 操作方法
为了方便管理 Token,我们可以将 Token 的存储和获取操作封装成工具函数:
// 封装token
// utils文件夹下index.tsx
const TOKENKEY = 'token_key' // 定义token的key
function setToken(token: string) {
localStorage.setItem(TOKENKEY, token)
}
function getToken() {
return localStorage.getItem(TOKENKEY)
}
export {
setToken,
getToken
}
setToken
:将 Token 存储到localStorage
中。getToken
:从localStorage
中获取 Token。
1.2 封装 Axios 请求
首先,我们需要封装 Axios 请求,并在请求拦截器中注入 Token 如果token存在,则将其添加到请求的 Authorization
头中
// axios 封装
import axios from 'axios'
import { getToken } from '@/utils'
const request = axios.create({
baseURL: 'http://geek.itheima.net/v1_0',
timeout: 5000
})
// 请求拦截器
request.interceptors.request.use((config) => {
// 获取当前令牌token
const token = getToken()
if (token) {
// bearer 认证+令牌字符串
config.headers.Authorization = `Bearer ${token}`
}
return config
}, e => {
return Promise.reject(e)
})
// 响应拦截器
request.interceptors.response.use(response => {
return response.data
}, e => {
return Promise.reject(e)
})
export default request
问题来了:页面刷新token可能丢失,那我们怎么办?
2、 解决 Token 刷新丢失问题
为了解决这个问题,我们需要在应用初始化时从 localStorage
中读取 Token,并将其存储到 Redux 状态中。
// 用户状态
import { createSlice } from '@reduxjs/toolkit'
import { setToken as _setToken, getToken } from '@/utils'
const userSlice = createSlice({
name: 'user',
initialState: {
token: getToken() || ''
},
reducers: {
setToken: (state, action) => {
state.token = action.payload
_setToken(action.payload)
}
}
})
const { setToken } = userSlice.actions
const userReducer = userSlice.reducer
// 异步获取 Token
const fetchLogin = (loginForm: { username: string; password: string }) => {
return async (dispatch: any) => {
try {
const res = await request.post('/authorizations', loginForm)
const token = res.data.token
dispatch(setToken(token))
} catch (error) {
console.log(error)
}
}
}
export { setToken, fetchLogin }
export default userReducer
- Redux Slice:我们使用
createSlice
创建了一个 Redux Slice(状态切片),用于管理用户状态和操作状态reducers。初始状态从localStorage
中读取 Token。使用reducers对象里面的setToken
方法,将获取token动作进行捕获 - setToken 是从
userSlice.actions
中解构出的action creator
,用于生成setToken
的action
。 - userReduce: 是
userSlice
生成的reducer
,用于处理状态更新 - 异步 Action:
fetchLogin
是一个异步 Action,用于发送登入请求并保存 Token。使用dispatch进行action操作
为了在应用中使用 Redux,我们需要配置 Store 并将统一导出store实例中:
// 导出redux子模块 + 导出store 实例
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './modules/user'
export default configureStore({
reducer: {
user: userReducer
}
})
这里的 configureStore
:用于创建 Redux Store,并将 userReducer
注册到 Store 中。
下面我们要实现全局共享token
3、根组件配置
在根组件中,我们需要使用 Provider
将 Redux Store 注入到应用中:
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import router from './router'
const root = ReactDOM.createRoot(document.getElementById('root')!)
root.render(
<>
<RouterProvider router={router}></RouterProvider>
</>
)
-
!
: 非空断言操作符,告诉 TypeScript(或开发者)document.getElementById('root')
一定不会返回null
-
Provider:将 Redux Store 传递给所有组件,使得组件可以通过
useSelector
和useDispatch
访问 Store 中的状态和派发 Action。 -
RouterProvider:负责将路由配置传递给应用。
扩展:路由权限控制
在某些情况下,我们需要对某些路由进行权限控制,确保只有登入用户才能访问。就像小红书下载内容前,自动跳转到登入页面
// 封装高阶组件
import { getToken } from "@/utils"
import { Navigate } from "react-router-dom"
export function AuthRoute({ children }) {
const token = getToken()
if (token) {
return <>{children}</>
} else {
return <Navigate to="/login" replace />
}
}
- AuthRoute:这是一个自行封装函数组件,用于检查用户是否登入。如果用户已登入(即 Token 存在),则渲染子组件;否则,重定向到登入页面。
4. 总结
通过以上步骤,我们实现了一个完整的登入页面 Token 验证与封装请求的解决方案。我们封装了 Axios 请求,并在请求拦截器中注入 Token;使用 Redux 管理用户状态,并在页面刷新时从 localStorage
中读取 Token;最后,通过高阶组件实现了路由权限控制。这套方案可以为中大型项目提供开箱即用的用户认证和权限管理功能。