React-crm的权限管理总结

引言

最近在写react-redux的后台管理系统,我觉得权限管理挺重要的,在此总结一下。

大致流程

  • 配置axios,解决代理以调用接口获取数据
  • 用户登陆调用接口,获取传入token以允许登陆,存储token
  • 在登陆时,收集用户信息(身份roles)存储以计算出权限
  • 路由分类(静态、动态)拿着用户身份和所有路由去计算出accessRoutes(可进路由)
  • 通过accessRoutes遍历出每一个route渲染到菜单栏中
  • 调整细节,路由定向,意外情况

配置代理

因为同源策略的原因,代理是们非常有必要的,否则会产生跨域问题。

安装axios

cnpm i axios@0.27.2 -S

image

具体代码如下

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

image
在@/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

image

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

image

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

image

将拿到的token存入本地和redux在@/store/ation的

fetchLogin(data).then(({token})=>{
    if(token){  //优化
            localStorage.setItem("token",token);
           dispath({type:"USER_LOGIN",payload:token})
    }
})

reducer处理同步action

image

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

image

在@/utils/request.js里

config.headers.Authorization = localStorage.getItem("token")
//代表每次请求的请求头都会带着token

模拟路由守卫

如图,将渲染路由的地方抽取成一个组件

image

image

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

image

发现arr还能在抽取一下

image

获取用户信息

image

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

image

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

image

计算出权限渲染路由

image

给动态路由添加可以允许哪些角色可以访问的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 })
    }
}

现在角色有了,全部的路由规则有了,算法有了,就可以生成当前用户可以访问的路由规则们,如下:

image

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

image

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

生成全部路由规则和Layout菜单

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

image

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

image

image

还需要进行跳转,如下:

image

优化一下权限设计

image

篡改token

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

image

书写resetUser这个action,如下:

image

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

完整的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;

总结

思考一下,前端权限校验的流程:

  1. 点击登录,调用接口,获取token。token需要存储到redux中,还需要存储到localstorage中。
  2. 带个token,再去调用一个接口,获取用户信息,用户信息中包含角色(role)信息。也需要存储到redux中。
  3. 使用后端返回 的role和路由表,执行算法,得到当前登录用户有权访问的路由们(accessRoutes)。也是需要存储到redux中。
  4. 根据accessRoutes动态生成路由规则和Layout菜单。
  5. 跳到后面首页面/,接着重定向到/dashboard。

image

posted @ 2023-03-03 22:29  Hccok  阅读(183)  评论(0)    收藏  举报