后台管理系统 – 权限设计

后台管理系统 – 权限设计一、前言对于前端项目特别是中后台管理系统项目,权限设计是最复杂的点之一。一般来说权限设计需要后端来把关,毕竟相对来说前端是无法保证安全的,前端的代码和数据请求都可以伪造。而前端的权限设计更多是为了用户体验的考虑。前端保证体验,后端保证安全。由于前后端的开发差异和侧重点不同,在权限设计上也不一样。后端更多的是根据功能对象划分不同的权限模块,针对接口相应进行权限判断;而前端更多是针对页面路由进行模块划分,针对页面可访问进行判断。接下来将以后台管理系统为例,分享个人对前端权限设计的见解。(具体内容尽量做

大家好,又见面了,我是你们的朋友全栈君。

一、前言

对于前端项目特别是中后台管理系统项目,权限设计是最复杂的点之一。

一般来说权限设计需要后端来把关,毕竟相对来说前端是无法保证安全的,前端的代码和数据请求都可以伪造。而前端的权限设计更多是为了用户体验的考虑。前端保证体验,后端保证安全。

由于前后端的开发差异和侧重点不同,在权限设计上也不一样。后端更多的是根据功能对象划分不同的权限模块,针对接口相应进行权限判断;而前端更多是针对页面路由进行模块划分,针对页面可访问进行判断。

接下来将以后台管理系统为例,分享个人对前端权限设计的见解。

(具体内容尽量做到和技术框架无关,无论是vue还是react都只是代码实现上的差异,主思路一致。不过话说vue的实现确实要比react简便很多,所以下述代码都以react为例)

二、页面级别

1、几种方式比较

先上几个常见的权限设计方式。

  • 方式一:由后端返回筛选后的路由配置,前端渲染

    • 描述:
      这种就是前端将所有路由配置数据给到后端存储,后端对根据用户权限对路由数据筛选后返回到前端,再由前端渲染。
    • 存在的问题:
      路由是前端使用的,为啥要存储到后端呢?要调整路由结构或者修改路由啥的都要找后端修改,前端开发不乐意,后端也不乐意,前后端分离的时代,这不是在倒退嘛。
  • 方式二:后端返回用户角色,前端根据角色做路由筛选

    • 描述:
      这种对方式一做了优化,方式一是后端根据权限筛选路由后返回前端,而现在是把筛选过程放在了前端,后端返回角色信息,前端遍历路由配置,根据角色筛选出有权限的路由渲染。
    • 存在的问题:
      但这种方式还有一个问题,就是角色的权限并非一成不变,一旦角色权限改了,前端的路由配置都要逐个排查修改,如果系统设计了动态修改角色权限的功能,那这种设计方式就没法用了。
  • 方式三:后端返回当前用户的所有权限id,前端根据权限id做路由筛选

    • 这也是本文推荐的方式,下面详细说明。

具体来说,就是对每一个页面路由都设置一个匹配的权限id(accessId),后端只需要把用户的所有权限id给到前端即可,不需要角色信息。

ps: 有些人可能对角色这点绕不过去,其实不管你的系统有没有角色这个概念,对于前端来说,角色只是一个对用户的一个称谓而已,在需要的时候展示这个称谓给用户界面。
一个角色可以有多个权限,然而前端不需要关心具体角色有哪些权限,前端需要的只是当前用户有哪些权限。
具体角色的权限数据只有在动态配置角色权限的页面才需要,实现上只需要遍历路由配置以一个tree树形组件展示即可,这种场景下也就是角色权限可能随时会变,前端就不应该以角色数据处理权限,而是应该以权限id来定,以不变应万变。
其实你也可以把每一个权限id都当成不同的角色理解,只不过这个角色就只有一个权限。

至于路由的权限id在哪里配置,这就看你项目的路由管理方案了,最好是对路由有一个统一管理,然后根据用户权限对路由做动态筛选,或者在路由访问时拦截判断。

2、导航菜单的处理

在这里插入图片描述

一般来说后台管理系统都会有个导航菜单,以侧边栏导航居多,对于用户来说这个也是所有页面的访问入口,所以导航菜单需要根据用户权限动态展示。

建议将所有路由配置信息存储在一个配置数组中,导航菜单就根据路由配置数组来动态生成,同时判断权限做筛选。

  • 对于 vue 来说,使用 vue-router 管理路由已经非常方便了;
  • 而 react 就有点麻烦,
    • 对于 react-router v5 及以下版本可以使用react-router-config来统一管理路由,
    • 对于 react-router v6 版本,安利一下个人封装的路由管理方案react-router-waiter传送门)。

路由配置示例:

const routes = [
  { 
   
    path: '/index',
    component: Index,
    meta: { 
    // meta,自定义的数据都放这里面
      title: '首页', // 菜单标题
      accessId: 10000, // 权限id
      hideMenu: false, // 是否在侧边栏隐藏当前路由菜单
      noLogin: false, // 当前路由访问是否需要登录
    },
  },
  { 
   
    path: '/nest',
    meta: { 
   
      title: '多级菜单',
    },
    children: [
      { 
   
        path: 'nest1',
        component: Nest1,
        meta: { 
   
          title: '二级菜单',
          accessId: 10001,
        }
      },
    ]
  },
]

导航菜单动态生成示例:

function getMenuList () { 
   
  const getList = (routeList = [], prePath = '') => { 
   
    let menuList = []
    // 遍历路由
    routeList.forEach(v => { 
   
      v.meta = v.meta || { 
   }
      // 排除不需要显示菜单的路由
      if (v.redirect || v.path === '*' || v.meta.hideMenu) { 
   
        return
      }
      // 排除没有访问权限的路由
      if (!getIsCanAccess(v.meta.accessId)) { 
   
        return
      }
      const currentPath = prePath + v.path
      if (v.children) { 
   
      	// 有嵌套路由,递归添加菜单
        menuList.push((
          <SubMenu key={ 
   currentPath} title={ 
   v.meta.title}>
            { 
   getList(v.children, currentPath + '/')}
          </SubMenu>
        ))
      } else { 
   
        // 无嵌套路由,菜单添加结束
        menuList.push((
          <ItemMenu key={ 
   currentPath}>
            <Link to={ 
   currentPath}>{ 
   v.meta.title}</Link>
          </ItemMenu>
        ))
      }
    })
    return menuList
  }
 
  return getList(routes)
}

3、路由访问控制

导航菜单动态生成一定程度上限制了用户访问无权限的路由,但还不够,用户如果跳转一个没有权限的路由,或者在地址栏手动输入没有权限的路由网址,也是能访问页面,这就需要处理。

一般用户的权限信息都是从接口异步获取,所以我们需要在用户打开项目进入页面之前先请求接口拿到权限信息,然后再做后续页面的展示,这样才能保证在用户手动输入url场景下能有效地进行权限判断和路由拦截。

两种方式:

  • 1、简单的,获取权限信息 – 筛选路由配置数据 – 渲染路由。即拿到权限信息后就对路由配置数据做个过滤,只保留有权限的路由数据,再渲染路由,让用户访问无权限的路由时展示404页面。
  • 2、复杂点,获取权限信息 – 渲染路由 – 路由拦截处理。即拿到权限信息后直接渲染完整路由数据,然后通过路由的导航守卫做判断拦截,这样可以控制用户访问无权限的路由时展示403页面及更多提示信息,自定义性更强。
    后台管理系统 - 权限设计

渲染路由前的控制,在入口组件App.vue或App.js里来写,代码示例:

import { 
    HashRouter } from 'react-router-dom'
import RouterWaiter from 'react-router-waiter'

export default function App () { 
   
  const [isRender, setIsRender] = useState(false)

  useEffect(() => { 
   
    // 解析url,获取path路由
    const path = getRoutePath()
    // 排除登录页等不需要权限的路由
    if (['/login'].includes(path)) { 
   
      setIsRender(true)
    } else { 
   
      // 判断是否已获取到权限信息
      if (!store.isGotUserInfo) { 
   
        api.getUserInfo().then(res => { 
   
          const data = res.data || { 
   }
          // 权限信息存储到store状态管理数据中
          store.setUserInfo(data) 
          // 获取完权限信息,放开路由渲染
          setIsRender(true)
        })
      }
    }
  }, [])

  return (
    <HashRouter>
      { 
   isRender ? <RouterWaiter /> : null}
    </HashRouter>
  )
}
  • vue的实现也简单,在App.vue里通过v-if绑定控制<router-view />即可。

4、路由拦截

这是对上述“路由访问控制”的方式2的补充说明。

要实现路由拦截,需要对每一个路由的访问都做前置判断。

  • 对于vue,有自带的路由全局导航守卫beforeEach,处理很方便。
  • 而react没有,只能自行封装,再次安利一下react-router-waiter,对路由拦截也做了封装处理。

拦截判断的代码示例:

meta = meta || { 
   } // 路由配置数据的meta字段
if (!meta.noLogin && store.isLogin) { 
    // 登录判断
  const { 
    accessId } = meta
  if (!store.isGotUserInfo) { 
    // 是否已获取到用户(权限)信息
      api.getUserInfo().then(res => { 
   
        const data = res.data || { 
   }
        store.setUserInfo(data)
		// 无权限时拦截跳转403页面
        if (!getIsCanAccess(accessId)) { 
   
          toPage403()
        }
      })
  } else { 
   
    if (!getIsCanAccess(accessId)) { 
   
      toPage403()
    }
  }
} else { 
   
  // 未登录时拦截跳转登录页
  toPageLoin()
}

三、按钮级别

按钮级别,即页面中更细粒度的权限控制。

这个其实就很简单了,只需要控制相关的dom是否展示即可。

每一个需要控制的操作区域dom都给分配一个权限id,然后判断该用户是否具有该权限,控制该区域dom的显示隐藏。

后端也只需要把所有页面权限id和按钮级别的权限id都一箩筐给到前端就行。

  • vue里通过v-if绑定dom来处理就行,封装一个公共的方法来判断是否具有权限,也可以封装一个自定义指令来处理,以权限id为入参,使用更方便。
  • react里也差不多,通过jsx里if控制,同样可以封装个公共方法,也可以封装成一个公共组件处理。

代码示例:

return (
  <div>
    { 
   getIsCanAccess('10008')
      ? (
      <div>我是权限dom1</div>
        )
      : null}

    <div>hello</div>

    { 
   getIsCanAccess('10009')
      ? (
      <div>我是权限dom2</div>
        )
      : null}
  </div>
)

四、其他

基于此权限设计方案,个人搭建了一个react后台管理系统react-antd-mobx-admin,里面有完整的权限设计代码,供参考。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/160343.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 微信小程序反编译教程(微信小程序反编译得到的是html)

    准备环境:Node.js环境8.10.0(本人正在使用版本)下载地址npm5.6.0(本人正在使用版本)(安装NodeJs白送npm)微信web开发工具下载地址安卓模拟器(安装完就已刷root)(MuMu模拟器–当时为了打游戏才下载的,这真的是一个游戏模拟器_(:з」∠)_)下载地址Github作者qwerty472123的反编译工具Git项目地址1.下载Git项…

  • 【剑指offer】删除字符也出现在一个字符串

    【剑指offer】删除字符也出现在一个字符串

  • Linux那些事儿之我是Hub(26)支持计划生育–看代码的理由

    Linux那些事儿之我是Hub(26)支持计划生育–看代码的理由北大校长马寅初先生曾斩钉截铁地跟讲:”中国人口太多是因为农村晚上没有电.”因此,为了支持计划生育这项基本国策,每一个男人都有义务认真看一下电源管理的代码.另一方面,虽然现在已经不住在农村了,但我一直坚定不移的认为,这个世界,最慢的是我家的网速,最快的是我家电表的转速.所以,为了了解如何让电表转速更慢,让我们一起来看看usb子系统里是如何支持电源管理的吧.上节说了应该从usb_sus…

  • matlab语法 axis on,matlabaxis

    matlab语法 axis on,matlabaxis编程语言中文网今天精心准备的是《matlabaxis》,下面是详解!Matlab里axis这个函数怎么用,举个例子!axis函数可以用于操作普通的坐标属性(轴的缩放和外观)。比如:axis([xminxmaxyminymax]):可以设置当前坐标轴x轴和y轴的限制范围axis([xminxmaxyminymaxzminzmaxcmincmax])可以设置x,y…

  • 十进制小数转二进制小数方法

    十进制小数转二进制小数方法十进制小数转二进制小数方法转自:http://www.cnblogs.com/upzone/articles/1389365.html十进制小数→→→→→二进制小数 方法:“乘2取整”对十进制小数乘2得到的整数部分和小数部分,整数部分既是相应的二进制数码,再用2乘小数部分(之前乘后得到新的小数部分),又得到整数和小数部分.如此不断重复,直到小数部分为0或达到精度要求为止

  • phpproxy建立代理服务器_proxy设计模式

    phpproxy建立代理服务器_proxy设计模式代理,指的就是一个角色代表另一个角色采取行动,就象生活中,一个红酒厂商,是不会直接把红酒零售客户的,都是通过代理来完成他的销售业务。而客户,也不用为了喝红酒而到处找工厂,他只要找到厂商在当地的代理就行了,具体红酒工厂在那里,客户不用关心,代理会帮他处理。代理模式,就是给某一对象提供代理对象,并由代理对象控制具体对象的引用。代理模式涉及的角色:抽象主题角色,声明了代理主题和真实主题的公共…

    2022年10月30日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号