React-crm的权限管理总结
引言
最近在写react-redux的后台管理系统,我觉得权限管理挺重要的,在此总结一下。
大致流程
- 配置axios,解决代理以调用接口获取数据
- 用户登陆调用接口,获取传入token以允许登陆,存储token
- 在登陆时,收集用户信息(身份roles)存储以计算出权限
- 路由分类(静态、动态)拿着用户身份和所有路由去计算出accessRoutes(可进路由)
- 通过accessRoutes遍历出每一个route渲染到菜单栏中
- 调整细节,路由定向,意外情况
配置代理
因为同源策略的原因,代理是们非常有必要的,否则会产生跨域问题。
安装axios
cnpm i axios@0.27.2 -S

具体代码如下
import axios from 'axios'
import {
message,
Modal
} from 'antd'
import {
ExclamationCircleOutlined
} from '@ant-design/icons'
import store from '@/store'
import {
resetUser
} from '@/store/actions'
const {
confirm
} = Modal
// 为了解决调接口遭遇同源策略的阻塞,这里“前端项目调用当前端口上的接口”
const baseURL = 'http://localhost:8080'
const version = '/api/react'
// 创建axios实例
const service = axios.create({
baseURL: baseURL + version,
timeout: 5000
})
// 添加请求拦截器
service.interceptors.request.use(
config => {
// 添加Token
config.headers.Authorization = localStorage.getItem('token')
return config
},
error => {
console.log(error) // for debug
return Promise.reject(error)
}
)
// 添加响应拦截器
service.interceptors.response.use(
response => {
// 如果代码走到这里,HTTP状态码=200
const res = response.data
console.log('----响应拦截器', res)
// 对业务状态码进行判断
if (res.err !== 0) {
// 如果业务状态码不等于0,表示业务失败,就把后端的反馈信息弹出来。
message.error(res.msg || '入参有误')
// 当Token过期或者Token是伪造的,要求重新登录。
if (res.err === -1) {
// 登录重新登录
confirm({
title: '当前你的登录已失效',
icon: < ExclamationCircleOutlined / > ,
content: '请重新登录',
okText: '重新登录',
// 隐藏取消按钮,要求必须重新登录
cancelButtonProps: {
style: {
display: 'none'
}
},
onOk() {
store.dispatch(resetUser())
}
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res.data
}
},
error => {
console.log('err' + error)
return Promise.reject(error)
}
)
export default service
配置代理proxy

在@/api/user.js简单封装一下登录接口
import request from '@/utils/request'
export function fetchLogin(data) {
return request({
url: '/user/login',
method: 'POST',
data
})
}
export function fetchUserInfo() {
return request({
url: '/user/info',
method: 'GET',
params: {}
})
}
获取token
在登录页面点击按钮时触发请求,这个请求需在redux里发送,类似vue中的action中发请求,但是在redux中默认不支持异步代码(ajax请求),所以需要安装第三方库react-thunk
cnpm i redux-thunk -S

然后在@/store/action.js里添加,这是单独抽取的文件,是ReduxCreator,action多了之后,就建一个文件专门储存,方便管理。

设置好action之后,在登录组件中导入然后使用

将拿到的token存入本地和redux在@/store/ation的
fetchLogin(data).then(({token})=>{
if(token){ //优化
localStorage.setItem("token",token);
dispath({type:"USER_LOGIN",payload:token})
}
})
reducer处理同步action

redux每次刷新数据都会不见,所以要进行数据持久化

在@/utils/request.js里
config.headers.Authorization = localStorage.getItem("token")
//代表每次请求的请求头都会带着token
模拟路由守卫
如图,将渲染路由的地方抽取成一个组件


在@/pages/index.js添加静态路由

发现arr还能在抽取一下

获取用户信息

getInfo是像action.js的login一样,调用异步

将得到的用户信息存入仓库

计算出权限渲染路由

给动态路由添加可以允许哪些角色可以访问的meta.roles属性
在@/store/permission.js算法,计算出可通过的路由
// roles 角色
// tmp 每一个规则
// hasPermission 判断一个角色是否有访问某个路由权限
// 如:admin meta: roles:["amdin","editor"]
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
// 没有meta
return true
}
}
// routes 所有动态路由
// roles 角色
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = {
...route
}
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
// generateRoutes 生成路由规则们
// asyncRoutes 所有动态路由
// roles 角色
export function generateRoutes(asyncRoutes, roles) {
return dispatch => {
let accessRoutes = []
// filterAsyncRoutes 生成当前用户可以访问的路由规则们
accessRoutes = filterAsyncRoutes(asyncRoutes, roles)
console.log("accessRoutes:::", accessRoutes);
// 把路由规则们存储到redux
// dispatch({ type: 'USER_PERMISSION', payload: accessRoutes })
}
}
现在角色有了,全部的路由规则有了,算法有了,就可以生成当前用户可以访问的路由规则们,如下:

然后,需要把路由规则们,存储到redux中,如下:

定义状态,让reducer接收信号,如下:

生成全部路由规则和Layout菜单
上面已经根据角色,计算出来,可以访问的路由规则们,然后生成全部的路由规则。你刚才计算出来的是动态路由规则,除了动态路由规则,还有静态路由规则,开始计算全部的路由规则,如下:

然后,根据规则,生成侧边菜单,如下:


还需要进行跳转,如下:

优化一下权限设计

篡改token
如果token被篡改,过期了,如下:

书写resetUser这个action,如下:

reducer接收到这个信号,处理之,如下:

完整的Permission.jsx的代码如下:
import { useRoutes, useNavigate, useLocation } from "react-router-dom"
import { useEffect, useMemo } from "react"
import { useSelector, useDispatch } from "react-redux"
import { getInfo } from "@/store/actions"
import { generateRoutes } from "@/store/permission"
import { constantRoutes, asyncRoutes } from "@/pages/index"
// 白名单
const whiteList = ['/login']
function Page() {
const dispatch = useDispatch();
const { token, roles, accessRoutes } = useSelector(state => state.user)
const navigator = useNavigate();
const { pathname } = useLocation();
// 监听token变化
useEffect(() => {
// 当token从无到有,有了token,才能获取用户信息
if (token) {
dispatch(getInfo())
} else {
navigator("/login", { replace: true })
}
}, [token])
useEffect(() => {
if (roles && roles.length > 0) {
// roles中有角色,配合路由表和算法,生成当前用户可以访问的路由规则们
// ....
dispatch(generateRoutes(asyncRoutes, roles))
}
}, [roles])
useEffect(() => {
if (accessRoutes && accessRoutes.length > 0) {
navigator("/dashboard", { replace: true })
}
}, [accessRoutes])
useEffect(() => {
// !whiteList.includes(pathname) 表示访问的路径并没有在白名单中
// 如果在白名单中,是不需要token
if (!whiteList.includes(pathname) && !token) {
navigator("/login", { replace: true })
}
// 表示已经登录了,已经有token了,又访问登录页面
if (token && pathname === "/login") {
navigator("/", { replace: true })
}
if (token && pathname === "/") {
navigator("/dashboard", { replace: true })
}
}, [pathname])
// 计算出全部的路由规则
const routes = useMemo(() => {
const result = [...constantRoutes]; // 静态路由规则copy一份
result[0].children = accessRoutes
return result;
}, [accessRoutes])
const element = useRoutes(constantRoutes)
return element;
}
export default Page;
总结
思考一下,前端权限校验的流程:
- 点击登录,调用接口,获取token。token需要存储到redux中,还需要存储到localstorage中。
- 带个token,再去调用一个接口,获取用户信息,用户信息中包含角色(role)信息。也需要存储到redux中。
- 使用后端返回 的role和路由表,执行算法,得到当前登录用户有权访问的路由们(accessRoutes)。也是需要存储到redux中。
- 根据accessRoutes动态生成路由规则和Layout菜单。
- 跳到后面首页面/,接着重定向到/dashboard。


浙公网安备 33010602011771号