• 前端根据登录用户做不同权限控制


    对于大部分管理后台而言,角色权限都是一个重要的环节。通过角色权限的配置,我们可以轻松的调整各个用户所拥有的各个模块或者说页面的权限,从而让用户只能访问到对应权限的页面。

    通俗易懂的来说,就是哪些页面是向所有用户开放的,哪些是需要登录后才能访问的,哪些是要拥有xx角色权限才能访问的等等(这里的xx指的是管理员、普通成员等这些的角色)。

    在后台管理系统中角色权限的方案设计是很重要的。

    • 其一,好的设计能为后面新增的模块或者说页面省下很多功夫。
    • 其二,好的设计能为之后的拓展功能(比如权限具体控制某个按钮等)提供更多的维护和设计思路。
    • 其三,好的设计可使代码可读性更强,更能一眼区分开权限代码与业务代码

    对于角色权限而言,真正进行把关的是应该是后端。首先是因为前端的相关代码校验是可以被数据造假通过的,安全性并不高。其次,在一个系统中前端所调用的接口是不应该被无权调用通过并且返回数据的。因此接口这块后端必须严格根据权限去控制,谨防无权限直接调用得到数据返回。简而言之就是即使前端没有把控页面和权限,用户也不能够获取没有权限的页面或者模块的相关数据和操作的,后端应该是可以判断他越权访问并拒绝返回数据的。但是若无前端把控,那这样整个系统的体验将会很糟糕,比如访问无权限页面时各种报错问题等等。因此前端在角色权限中更多职责的应是完善用户的交互体验

    角色权限控制的整个流程中,前端整个流程步骤应是首先展示无需登录的默认页面(比如404页面、登录页、注册页),然后在登录或浏览器刷新时调用后端接口拿到后端给的该账户的权限数据,然后将数据注入到系统中,整个系统拿到权限数据后就开始对页面的展现内容以及页面导航进行生成,最终生成一个只展示当前用户所拥有对应权限的系统。从而达到整个角色权限的控制。综上所述,前端在角色权限中更多职责的应是完善用户的交互体验。

    从下面三个方面,讲述前端角色权限的实现

    登录权限控制

    登录权限控制,简而言之就是实现哪些页面能被未登录的用户访问,哪些页面只有用户登录后才能被访问。

    实现这个功能也很简单,下面例举出 2 种常见的实现方案。

    第一种为将无需登录的页面路由放在一起,代码如下:

    1. let invisible = [
    2. {
    3. path: '/login', //登录页面
    4. name: 'Login',
    5. component: Login,
    6. },
    7. {
    8. path: '/404',
    9. name: 'index-notFount',
    10. component: () => import('@/pages/core/NotFount/index'),
    11. },
    12. ];
    13. export default invisible;

     定义一个invisible数组,数组中包含着所有无需登录就可以查看的页面路由。

    1. // 引入无需登录的页面
    2. import invisible from './invisible';
    3. let router = new Router({
    4. routes: [
    5. ...invisible,
    6. ],
    7. });
    8. const invisibleMap = [];
    9. invisible.forEach(item => {
    10. if (item.name) {
    11. invisibleMap.push(item.name);
    12. }
    13. });
    14. router.beforeEach(async (to, from, next) => {
    15. if (!invisibleMap.includes(to.name)) {
    16. // 业务逻辑判断登录等
    17. }
    18. else {
    19. next();
    20. }
    21. })

     引入invisible,路由名称映射到invisibleMap数组上,在路由守卫中拦截判断。由此做到无需登录的页面可以直接查看(放在invisible数组中),需要登录的页面则会进行登录等业务判断。

    除上述方法外,也可在路由对象中以添加meta的方式去实现登录页面权限控制,相关代码如下:

    1. export const routes = [
    2. {
    3. path: '/login', //登录页面
    4. name: 'Login',
    5. component: Login,
    6. },
    7. {
    8. path:"/list", // 列表页
    9. name:"List",
    10. meta:{
    11. need_login:true //需要登录
    12. }
    13. }
    14. ]

     代码如上所示,登录页面由于无需登录,因此可不用设置meta.need_login属性,然而列表页面需要登录,因此设置need_login属性。

    同第一种方式一样,真正的拦截在路由守卫之中,代码如下:

    1. router.beforeEach((to, from, next) => {
    2. if (to.meta.need_login) {
    3. // 业务逻辑判断登录等
    4. } else {
    5. next();
    6. }
    7. });

    此处拿到need_login字段,判断是否为需要登录的路由页面,如若是,则进行下一步登录逻辑判断等,如若不是,则可放行。

    角色权限控制

    在讨论角色权限控制之前,我们应该先清楚一个点:在引入了角色概念的系统中,任何该系统中的账号都应该至少拥有一个或几个角色身份,这样该账号就拥有当前这一个角色(或几个角色)的相关权限功能。简而言之,我们不会直接把权限赋予给用户,而是通过角色来赋予给用户。角色权限控制主要是解决给不同角色赋予不同权限从而赋予不同账户权限,接下来先了解一下角色的概念。

    在某个系统当中,存在3个比较普遍性的角色:普通成员、管理员以及超级管理员。普通成员能够浏览系统的 a、b、c 三个模块,但是它不能查看和编辑 d、e 模块(假设只有d、e模块可编辑)。管理员拥有普通会员的所有权限,另外它还能查看 d、e 模块和编辑d模块。超级管理员拥有此系统的所有权限,因此相比于管理员而言,在这就多出一个编辑e模块。

    当然,就上述而言,都是一些简单的角色划分。本文不在此做更多的深讨。

    那么角色权限,在设计上能否以前端为主导呢?(即后端只为账号标识为某某角色,控制角色权限这块由前端主导)

    我们通过一个简单的例子来回答上述问题。

    我们暂且根据以上的较为普遍的角色来做简单设计。

    1. export const permission = {
    2. member:["Home"], //普通成员
    3. admin:["Home" ,"Notify"], // 管理员
    4. super_admin:["Home" ,"Notify","Manage"] // 超级管理员
    5. }

    在上述角色权限中,普通成员拥有首页权限,管理员拥有首页、通知权限,超级管理员则还额外拥有管理的权限。

    如果以前端为主导,那么后端则应是在登录接口返回当前账户所属哪些角色。拿到该账号的角色后后就去上面的配置文件里取出该角色所能访问的页面权限,随后将这部分页面权限加载到系统中从而达到权限控制的目的(需要注意的是,数组里面的值应和对应页面的路由名称相匹配)。

    在上述设计中,后端只负责给账户标识对应角色,并且写入库中,在登录时返回给前端此账户对应的角色。到了这一步,可能会有同学存在疑问,这样不是也挺好的吗,前端不也可以控制角色权限嘛。别急,那现在来思考一个问题。如果对一个已上线的系统项目,现需要紧急新增一个角色比如x,那么前端就要急需修改配置文件(配置文件如上图),此时还不够,还需把之前的y用户移动到x角色下,那么此时不光是前端要改配置文件,后端也需要在库中把y用户移动到x角色下。这样的改动显得非常容易出错且复杂。

    综上所述,在角色权限这块,其实最好的办法就是交给后端去配置,有哪些角色,账户对应哪些角色这些逻辑应当是后端负责,后端通过登录直接返回该账户所拥有的权限,前端这块无需过度关注角色主要职责应是根据后端的权限返回,展示对应的权限页面和菜单。这样即使碰到上述的修改也能轻便灵巧的解决。

    以下介绍角色权限的方案。

    如后端返回的账户权限结构如下:

    1. {
    2. "home": {
    3. "id":"100",
    4. "name":"home",
    5. "desc":"首页",
    6. "value":true,
    7. "children": [],
    8. }
    9. }

     

    在这个权限结构之中,id为页面或者说模块的唯一标识id,name此处最好与前端路由页面对象的name值相对应,desc为菜单上展示的名称,value代表这个模块或者页面是否展示,children数组为此页面的二级页面数组,对于路由的权限控制和菜单的渲染生成都有着重要的影响。

    在此结构中,前端通过判断 value 来决定这个页面是否有权限展示,children 下为当前页面或者说模块下的二级页面,三级页面等,结构跟 home 应是一样的。如若一级页面value为false,那下面的二级、三级应当都无权展示。

    此时前端需要做的是递归遍历后端返回的这个结构,当判断value为 false 的时候,把对应到的路由页面给过滤掉。

    1. // 生成过滤路由和菜单的方法
    2. function filterRouter(arr, obj, type) {
    3. if (Array.isArray(obj)) {
    4. // 数组处理
    5. obj.forEach(item => {
    6. handleRouterItem(arr, item, type);
    7. });
    8. } else {
    9. // 对象处理
    10. for (let item in obj) {
    11. handleRouterItem(arr, obj[item], type);
    12. }
    13. }
    14. }
    15. // 处理每个元素节点
    16. function handleRouterItem(arr, item, type) {
    17. // 确定这个页面或模块是不展示的
    18. if (item.value === false) {
    19. if (type === 'menu') {
    20. assistance(arr, routerMap[item.name]);
    21. } else {
    22. assistanceRouter(arr, routerMap[item.name]);
    23. }
    24. } else if (item.childrens && item.childrens.length > 0) {
    25. filterRouter(arr, item.childrens, type);
    26. }
    27. }
    28. function assistanceRouter(arr, name, obj) {
    29. for (let i = 0; i < arr.length; i++) {
    30. if (arr[i].name === name) {
    31. // 无权限页面设置meta字段或者直接删除
    32. // arr.splice(i, 1);
    33. Vue.prototype.$set(arr[i].meta, 'hasRoleAuth', false);
    34. return true;
    35. } else {
    36. if (arr[i].children && arr[i].children.length > 0) {
    37. if (assistanceRouter(arr[i].children, name, arr[i])) {
    38. return;
    39. }
    40. }
    41. }
    42. }
    43. }
    44. function assistance(arr, name, obj) {
    45. for (let i = 0; i < arr.length; i++) {
    46. if (arr[i].name === name) {
    47. arr.splice(i, 1);
    48. return true;
    49. } else {
    50. if (arr[i].children && arr[i].children.length > 0) {
    51. if (assistance(arr[i].children, name, arr[i])) {
    52. return;
    53. }
    54. }
    55. }
    56. }
    57. }
    58. export const rolePermission = () => {
    59. // router为所有页面的路由结构,roleRouter为后端返回的角色权限对象
    60. filterRouter(router, roleRouter);
    61. router.addRoutes(router);
    62. }

  • 相关阅读:
    【重识云原生】第六章容器6.1.7.1节——Docker核心技术cgroups综述
    测试的专用
    《中台产品经理宝典》图书连载:使用动作分析法实现业务中台化抽象
    sql实例总结
    C++ Reference: Standard C++ Library reference: C Library: cwchar: wcsstr
    C++11—线程库
    ptc热烫头切纸控制
    react 跨域的两种方法
    【C++】类和对象 (下篇)
    基于ssm的养老智慧服务平台毕业设计源码071526
  • 原文地址:https://blog.csdn.net/m0_50105168/article/details/125563187