Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >前端如何配合后端完成RBAC权限控制

前端如何配合后端完成RBAC权限控制

作者头像
FairyEver
发布于 2019-07-26 07:04:46
发布于 2019-07-26 07:04:46
2.6K00
代码可运行
举报
文章被收录于专栏:今日前端今日前端
运行总次数:0
代码可运行

https://juejin.im/post/5c1f8d6c6fb9a049e06353aa

微信排版对代码型文章很不友好,建议读着点击最下方阅读原文查看原始文章。

承蒙 D2Projects开源组 的邀请,就写了这篇文章,平时写的不多,失误之处,请大家多多包涵。


因为工作的原因要找一个管理端模板,用于开发一个网银系统的前端界面骨架,我就找到了d2-admin,看着十分对胃口,接着我就想着先做一个前后端分离的demo,来看一下d2-admin模板是否满足我们项目的需求,那么一个权限管理demo我觉得很适合拿来做实验,就此我做了jiiiiiin-security权限系统项目。

为什么我们需要前端实现RBAC

在说我们前端为什么要实现权限控制之前,大家势必要了解一下我们要实现的东西的本质是什么,下面简单引用两句介绍:

RBAC 以角色为基础的访问控制(英语:Role-based access controlRBAC),RBAC认为权限授权实际上是Who、What、How的问题。在RBAC模型中,who、what、how构成了访问权限三元组,也就是“Who对What(Which)进行How的操作”。 RBAC是一种思想,任何编程语言都可以实现,其成熟简单的控制思想 越来越受广大开发人员喜欢。

更多内容,请大家不熟悉的势必自行google;

我认为前后端是相辅相成的,所以要做好前端的权限控制,如果能提前了解后端的权限分配规则和数据结构是能够更好的进行相互配合的,当然如果完全不理会后台的权限划分,硬性来做上面的两个需求也是能实现的,只是不掌握全局,就很难理解这样做的意义何在,所以建议大家在考虑这个问题的时候(这里指前端同学),还是要大概去看看RBAC的概念,属性经典的表结构,从而属性后台权限分别的业务规则。

“权限管理”一般大家的印象中都属于后端的责任,但是这两年随着SPA应用的兴起,很多应用都采用了前后端分离的方式进行开发,但是纯前端的开发方式就导致很多以前由后端模板语言硬件解决的问题,现在势必要重新造一次轮子,而这个时候前端我认为是配合后端对应语言的安全框架根据自身的业务需要来实现,在这里就说说我们的需求:

  1. 完善我们自己的Vue插件vue-viewplus的业务模块(这个插件是我们经过一年的内部使用,用来将一些开发应用所需的公共需求,抽取为一个个模块,方便进行快速的应用开发所写)
  2. 我们认为如果在前端根据后端配置的权限规则就能拦截一些不必要的请求,就能减少后端不必要的资源损耗,也能更快的提示正常用户
  3. 我们需要解决管理端界面菜单和按钮根据后端权限配置隐藏显示的需求
  4. 我们需要解决前端视图可访问性根据后端权限配置动态调整的需求

以上2、3、4点在前后端不曾分离的时候,这些事情都是由后类html模板语言(如传统的java中的jsp)所包办的,类似这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<html>
<sec:authorize access="hasRole('supervisor')">

This content will only be visible to users who have
the "supervisor" authority in their list of <tt>GrantedAuthority</tt>s.

</sec:authorize>
</html>

https://docs.spring.io/spring-security/site/docs/3.0.x/reference/taglibs.html

实现目标

  • 我们希望在进行页面导航的时候能先根据登录用户所具有的权限判断用户是否能访问该页面
  • 实现可见页面的局部UI组件的可使用性或可见性控制,即基于自定义v-access指令,对比声明的接口或资源别是否已经授权
  • 实现发送请求前对待请求接口进行权限检查,如果用户不具有访问该后端接口的权限,则不发送请求,而是友好的提示用户

实现方式

要实现【我们希望在进行页面导航的时候能先根据登录用户所具有的权限判断用户是否能访问该页面】这个目标,我们的方案是:
  1. 获得登录用户的可访问前端页面的path列表
  2. 一个公共的path列表
  3. router进行导航的beforeEach前置钩子中判断当前用户所请求的页面是否在以上两个集合之中,如果是则放行,如果不是,则通知插件调用方,让其自己处理失败的情况

下面是代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * RBAC权限控制模块
 */
import _ from 'lodash';
let _onPathCheckFail
let _publicPaths = []
let _authorizedPaths = []

/**
 * 是否是【超级管理员】
 * 如果登录用户是这个`角色`,那么就无需进行各种授权控制检测
 * @type {boolean}
 * @private
 */
let _superAdminStatus = false

const _compare = function(rule, path) {
  let temp = false
  if (_.isRegExp(rule)) {
    temp = rule.test(path)
  } else {
    temp = _.isEqual(path, rule)
  }
  return temp
}

/**
 * 检测登录用户是否具有访问对应页面的权限
 * 1.校验是否登录
 * 2.校验带访问的页面是否在`loginStateCheck#authorizedPaths`授权`paths`集合中
 * @param to
 * @param from
 * @param next
 * @private
 */
const _rbacPathCheck = function(to, from, next) {
  if (_superAdminStatus) {
    next();
    return;
  }
  try {
    // 默认认为所有资源都需要进行权限控制
    let isAllow = false
    const path = to.path;
    // 先检测公共页面集合
    const publicPathsLength = _publicPaths.length
    for (let i = publicPathsLength; i--;) {
      const rule = _publicPaths[i];
      isAllow = _compare(rule, path)
      if (isAllow) {
        break;
      }
    }
    // 非公共页面 && 已经登录
    if (!isAllow && this.isLogin()) {
      // 检测已授权页面集合
      const authorizedPathsLength = _authorizedPaths.length;
      for (let i = authorizedPathsLength; i--;) {
        const rule = _authorizedPaths[i];
        isAllow = _compare(rule, path);
        if (isAllow) {
          break;
        }
      }
    }

    if (isAllow) {
      next();
    } else {
      if (_.isFunction(_onPathCheckFail)) {
        if (_debug) {
          console.error(`[v+] RBAC模块检测:用户无权访问【${path}】,回调onPathCheckFail钩子`);
        }
        this::_onPathCheckFail(to, from, next);
      } else {
        next(new Error('check_authorize_paths_fail'));
      }
    }
  } catch (e) {
    if (_debug) {
      console.error(`[v+] RBAC模块检测出错: ${e.message}`);
    }
    if (_.isFunction(_errorHandler)) {
      this::_errorHandler(e)
    }
  }
};

const rbacModel = {
  /**
   * 【可选】有些系统存在一个超级用户角色,其可以访问任何资源、页面,故如果设置,针对这个登录用户将不会做任何权限校验,以便节省前端资源
   * @param status
   */
  rabcUpdateSuperAdminStatus(status) {
    _superAdminStatus = status;
    this.cacheSaveToSessionStore('AUTHORIZED_SUPER_ADMIN_STATUS', _superAdminStatus)
  },
  /**
   * 添加授权路径集合
   * 如:登录完成之后,将用户被授权可以访问的页面`paths`添加到`LoginStateCheck#authorizedPaths`中
   * @param paths
   */
  rabcAddAuthorizedPaths(paths) {
    this::rbacModel.rabcUpdateAuthorizedPaths(_.concat(_authorizedPaths, paths))
  },
  /**
   * 更新授权路径集合
   * @param paths
   */
  rabcUpdateAuthorizedPaths(paths) {
    _authorizedPaths = [...new Set(paths)]
    this.cacheSaveToSessionStore('AUTHORIZED_PATHS', _authorizedPaths)
  },
  /**
   * 更新公共路径集合
   * @param paths
   */
  rabcUpdatePublicPaths(paths) {
    _publicPaths = [...new Set(paths)];
    this.cacheSaveToSessionStore('PUBLIC_PATHS', _publicPaths)
  },
  /**
   * 添加公共路径集合
   * @param paths
   */
  rabcAddPublicPaths(paths) {
    this::rbacModel.rabcUpdatePublicPaths(_.concat(_publicPaths, paths))
  },
  install(Vue, {
    /**
     * [*] 系统公共路由path路径集合,即可以让任何人访问的页面路径
     * {Array<Object>}
     * <p>
     *   比如登录页面的path,因为登录之前我们是无法判断用户是否可以访问某个页面的,故需要这个配置,当然如果需要这个配置也可以在初始化插件之前从服务器端获取,这样前后端动态性就更高,但是一般没有这种需求:)
     * <p>
     * 数组中的item,可以是一个**正则表达式字面量**,如`[/^((\/Interbus)(?!\/SubMenu)\/.+)$/]`,也可以是一个字符串
     * <p>
     * 匹配规则:如果在`LoginStateCheck#publicPaths`**系统公共路由path路径集合**中,那么就直接跳过权限校验
     */
    publicPaths = [],
    /**
     * [*] 登录用户拥有访问权限的路由path路径集合
     * {Array<Object>}
     * <p>
     * 数组中的item,可以是一个**正则表达式字面量**,如`[/^((\/Interbus)(?!\/SubMenu)\/.+)$/]`,也可以是一个字符串
     * <p>
     * 匹配规则:如果在`LoginStateCheck#authorizedPaths`**需要身份认证规则集**中,那么就需要查看用户是否登录,如果没有登录就拒绝访问
     */
    authorizedPaths = [],
    /**
     * [*] `$vp::onPathCheckFail(to, from, next)`
     * <p>
     * 访问前端页面时权限检查失败时被回调
     */
    onPathCheckFail = null,
  } = {}) {
    _onPathCheckFail = onPathCheckFail;
    router.beforeEach((to, from, next) => {
      this::_rbacPathCheck(to, from, next);
    });
  }
};

export default rbacModel;

这里解释一下:

  1. 整个代码最终导出了一个普通的json对象,作为vue-viewplus的一个自定义模块,将会被mixin到其插件内部作为一个自定义模块: // 应用入口mian.js import Vue from 'vue' import router from './router' import ViewPlus from 'vue-viewplus' import viewPlusOptions from '@/plugin/vue-viewplus' import rbacModule from '@/plugin/vue-viewplus/rbac.js' Vue.use(ViewPlus, viewPlusOptions) ViewPlus.mixin(Vue, rbacModule, { moduleName: '自定义RBAC', router, publicPaths: ['/login'], onPathCheckFail(to, from, next) { NProgress.done() const title = to.meta.title this.dialog(您无权访问【${_.isNil(title) ? to.path : title}】页面) .then(() => { // 没有登录的时候跳转到登录界面 // 携带上登陆成功之后需要跳转的页面完整路径 next(false) }) } }) 大家如果没有使用或者不想使用这个插件(vue-viewplus也无所谓,这里只要知道,导出的这个对象的install会在应用入口被调用,并传入几个install方法几个必须的参数:
  • 路由对象
  • 应用的公共页面paths列表
  • 权限校验失败之后的处理函数 这样我们就能在初始化函数中缓存应用公共页面paths列表,注册路由钩子,监听路由变化。 这里我使用这个插件为的还有第二个目的,利用其来管理用户登录状态,详细看下面我为什么要使用这个状态
  1. 在监听到某个公共页面访问的时候,_rbacPathCheck函数将会:
  • 首先判断当前用户是否是超级管理员,你可以理解为linux中的root用户,如果是则直接放行,这样做是为了减少判断带来的开销,当然如果需要实现这个效果,需要在登录之后,根据后端返回的用户信息中查看用户的角色,是否是超级管理员,如果是,则调用文件导出的rabcUpdateSuperAdminStatus方法,在这里是页面实例的this.$vp.rabcUpdateSuperAdminStatus方法(vue-viewplus将每个模块导出的api绑定到页面实例即vm的

refs.loginForm.validate(valid => { if (valid) { // 登录 this.login({ vm: this, username: this.formLogin.username, password: this.formLogin.password, imageCode: this.formLogin.code }).then((res) => { // 修改用户登录状态 this.

vp.toast('登录成功', { type: 'success' }); // 重定向对象不存在则返回顶层路径 this.

route.query.redirect || '/') }) } else { // 登录表单校验失败 this.$message.error('表单校验失败') } }) }

  • 如果不是则检测待访问的页面的path是否在应用的公共页面paths列表_publicPaths中,如果是则放行 而做这个判断的前提是应用登录成功之后需要将其获得授权的前端paths设置this.$vp.rabcUpdateAuthorizedPaths给插件: js submit() { this.$refs.loginForm.validate(valid => { if (valid) { // 登录 this.login({ vm: this, username: this.formLogin.username, password: this.formLogin.password, imageCode: this.formLogin.code }).then((res) => { this.$vp.rabcUpdateAuthorizedPaths(authorizeResources.paths); }) } else { // 登录表单校验失败 this.$message.error('表单校验失败') } }) } 数据的格式如下: js ["/mngauth/admin", "/index", "/mngauth"] 并且,数组的值支持为正则表达式;
  • 如果不是则检查待访问页面的path是否在登录用户拥有访问权限的路由path路径集合_authorizedPaths中,如果是则放行,如果不是则整个校验结束,判断用户无权访问该页面,调用_onPathCheckFail回调函数,通知应用,这里应用则会打印dialog提示用户 因为我们的目的是抽象整个业务,所以这里才以回调的方式让应用有实际去感知和处理这一情况; 这样我们就完成了第一个目标;
要实现【实现可见页面的局部UI组件的可使用性或可见性控制,即基于自定义`v-access`指令,对比声明的接口或资源别是否已经授权】这个目标,我们的方案是:
  1. 获得登录用户的:
  • 被授权角色所拥有的资源列表,对应的资源别名 数据格式类似: json ["MNG_USERMNG", "MNG_ROLEMNG"]
  • 被授权角色所拥有的资源列表(或资源)所对应的后端接口集合 数据格式类似: js ["admin/dels/*", "admin/search/*/*/*", "admin/*/*/*", "role/list/*", "admin/*"] 但是默认希望的是RESTful格式: json [{url: "admin/dels/*", method: "DELETE"}, ....] 当然同样支持js正则表达式; 通过以上两组(二选一)授权数据,我们就可以对比用户在指令中声明的条件权限进行对比。
  1. 定义一个Vue指令,这里命名为access,其需要具备以下特点:
  • 可以让用户声明不同的权限表达式,如这个按钮是需要一组接口,还是一个资源别名
  • 可以让用户控制,在不满足权限检查之后,是让UI组件不显示还是让其不可用 当然要理解上面的数据结构后端是怎么构建的,可以参考表结构和权限说明

我们继续往上面的代码中添加逻辑,下面是代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const rbacModel = {
  //....
  /**
   * 更新授权接口集合
   * @param interfaces
   */
  rabcUpdateAuthorizeInterfaces(interfaces) {
    _authorizeInterfaces = [...new Set(interfaces)]
    this.cacheSaveToSessionStore('AUTHORIZED_INTERFACES', _authorizeInterfaces)
  },
  /**
   * 添加授权接口集合
   * @param interfaces
   */
  rabcAddAuthorizeInterfaces(interfaces) {
    this::rbacModel.rabcUpdateAuthorizeInterfaces(_.concat(_authorizeInterfaces, interfaces))
  },
  /**
   * 更新资源别名集合
   * @param alias
   */
  rabcUpdateAuthorizeResourceAlias(alias) {
    _authorizeResourceAlias = [...new Set(alias)]
    this.cacheSaveToSessionStore('AUTHORIZED_RESOURCE_ALIAS', _authorizeResourceAlias)
  },
  /**
   * 添加资源别名集合
   * @param alias
   */
  rabcAddAuthorizeResourceAlias(alias) {
    this::rbacModel.rabcUpdateAuthorizeResourceAlias(_.concat(_authorizeResourceAlias, alias))
  },
  install(Vue, {
    //....
    /**
     * [可选] 登录用户拥有访问权限的资源别名集合
     * {Array<Object>}
     * <p>
     * 数组中的item,可以是一个**正则表达式字面量**,如`[/^((\/Interbus)(?!\/SubMenu)\/.+)$/]`,也可以是一个字符串
     * <p>
     * 匹配规则:因为如果都用`LoginStateCheck#authorizeInterfaces`接口进行匹配,可能有一种情况,访问一个资源,其需要n个接口,那么我们在配置配置权限指令:v-access="[n, n....]"的时候就需要声明所有需要的接口,就会需要对比多次,
     * 当我们系统的接口集合很大的时候,势必会成为一个瓶颈,故我们可以为资源声明一个别名,这个别名则可以代表这n个接口,这样的话就从n+减少到n次匹配;
     */
    authorizeResourceAlias = [],
    /**
     * [*] 登录用户拥有访问权限的后台接口集合
     * {Array<Object>}
     * <p>
     *   1.在`v-access`指令配置为url(默认)校验格式时,将会使用该集合和指令声明的待审查授权接口列表进行匹配,如果匹配成功,则指令校验通过,否则校验不通过,会将对应dom元素进行处理
     *   2.TODO 将会用于在发送ajax请求之前,对待请求的接口和当前集合进行匹配,如果匹配失败说明用户就没有请求权限,则直接不发送后台请求,减少后端不必要的资源浪费
     * <p>
     * 数组中的item,可以是一个**正则表达式字面量**,如`[/^((\/Interbus)(?!\/SubMenu)\/.+)$/]`,也可以是一个字符串
     * <p>
     * 匹配规则:将会用于在发送ajax请求之前,对待请求的接口和当前集合进行匹配,如果匹配失败说明用户就没有请求权限,则直接不发送后台请求,减少后端不必要的资源浪费
     * <p>
     *   注意需要根据`isRESTfulInterfaces`属性的值,来判断当前集合的数据类型:
     *
     * 如果`isRESTfulInterfaces`设置为`false`,则使用下面的格式:
     * ```json
     * ["admin/dels/*", ...]
     * ```
     * 如果`isRESTfulInterfaces`设置为`true`,**注意这是默认设置**,则使用下面的格式:
     * ```json
     * [[{url: "admin/dels/*", method: "DELETE"}, ...]]
     * ```
     */
    authorizeInterfaces = [],
    /**
     * [*] 声明`authorizeInterfaces`集合存储的是RESTful类型的接口还是常规接口
     * 1. 如果是(true),则`authorizeInterfaces`集合需要存储的结构就是:
     * [{url: 'admin/dels/*', method: 'DELETE'}]
     * 即进行接口匹配的时候会校验类型
     * 2. 如果不是(false),则`authorizeInterfaces`集合需要存储的结构就是,即不区分接口类型:
     * ['admin/dels/*']
     */
    isRESTfulInterfaces = true
  } = {}) {
    //....
    this::_createRBACDirective(Vue)
  }
};

export default rbacModel;

首先我们在插件中添加几个字段和对应的设置接口:

  • isRESTfulInterfaces
  • authorizeInterfaces
  • authorizeResourceAlias

这样我们就可以维护用户拥有的授权资源别名列表、资源(对应接口)后端接口数据列表,并默认认为接口为RESTful数据结构;

接着我们就可以定义指令(在插件初始化方法install中),并在指令的bind声明周期,解析对应UI组件声明的所需权限信息,并和持有的资源列表进行对比,如果对比失败则对UI组件做相应的显示或者disable操作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 推荐使用资源标识配置:`v-access:alias[.disable]="'LOGIN'"` 前提需要注入身份认证用户所拥有的**授权资源标识集合**,因为这种方式可以较少比较的次数
 * 传统使用接口配置:`v-access:[url][.disable]="'admin'"` 前提需要注入身份认证用户所拥有的**授权接口集合**
 * 两种都支持数组配置
 * v-access:alias[.disable]="['LOGIN', 'WELCOME']"
 * v-access:[url][.disable]="['admin', 'admin/*']"
 * 针对于RESTful类型接口:
 * v-access="[{url: 'admin/search/*', method: 'POST'}]"
 * 默认使用url模式,因为这种方式比较通用
 * v-access="['admin', 'admin/*']"
 * <p>
 *   其中`[.disbale]`用来标明在检测用户不具有对当前声明的权限时,将会把当前声明指令的`el`元素添加`el.disabled = true`,默认则是影藏元素:`el.style.display = 'none'`
 * <p>
 *   举例:`<el-form v-access="['admin/search']" slot="search-inner-box" :inline="true" :model="searchForm" :rules="searchRules" ref="ruleSearchForm" class="demo-form-inline">...</el-form>`
 *   上面这个检索表单需要登录用户具有访问`'admin/search'`接口的权限,才会显示
 * @param Vue
 * @private
 */
const _createRBACDirective = function(Vue) {
  Vue.directive('access', {
    bind: function(el, { value, arg, modifiers }) {
      if (_superAdminStatus) {
        return;
      }
      let isAllow = false
      const statementAuth = _parseAccessDirectiveValue2Arr(value)
      switch (arg) {
        case 'alias':
          isAllow = _checkPermission(statementAuth, _authorizeResourceAlias)
          break
        // 默认使用url模式
        case 'url':
        default:
          if (_isRESTfulInterfaces) {
            isAllow = _checkPermissionRESTful(statementAuth, _authorizeInterfaces)
          } else {
            isAllow = _checkPermission(statementAuth, _authorizeInterfaces)
          }
      }

      if (!isAllow) {
        if (_debug) {
          console.warn(`[v+] RBAC access权限检测不通过:用户无权访问【${_.isObject(value) ? JSON.stringify(value) : value}`);
        }
        if (_.has(modifiers, 'disable')) {
          el.disabled = true;
          el.style.opacity = '0.5'
        } else {
          el.style.display = 'none';
        }
      }
    }
  })
}


/**
 * 校验给定指令显示声明所需列表是否包含于身份认证用户所具有的权限集合中,如果是则返回`true`标识权限校验通过
 * @param statementAuth
 * @param authorizeCollection
 * @returns {boolean}
 * @private
 */
const _checkPermission = function(statementAuth, authorizeCollection) {
  let voter = []
  statementAuth.forEach(url => {
    voter.push(authorizeCollection.includes(url))
  })
  return !voter.includes(false)
}

/**
 * {@link _checkPermission} 附加了对接口类型的校验
 * @param statementAuth
 * @param authorizeCollection
 * @returns {boolean}
 * @private
 */
const _checkPermissionRESTful = function(statementAuth, authorizeCollection) {
  let voter = []
  const expectedSize = statementAuth.length
  const size = authorizeCollection.length
  for (let i = 0; i < size; i++) {
    const itf = authorizeCollection[i]
    if (_.find(statementAuth, itf)) {
      voter.push(true)
      // 移除判断成功的声明权限对象
      statementAuth.splice(i, 1)
    }
  }
  // 如果投票得到的true含量和需要判断的声明权限长度一致,则标识校验通过
  return voter.length === expectedSize
}

const _parseAccessDirectiveValue2Arr = function(value) {
  let params = []
  if (_.isString(value) || _.isPlainObject(value)) {
    params.push(value)
  } else if (_.isArray(value)) {
    params = value
  } else {
    throw new Error('access 配置的授权标识符不正确,请检查')
  }
  return params
}

在使用指令之前,我们还需要解决插件所需权限列表的设置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
submit() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          // 登录
          this.login({
            vm: this,
            username: this.formLogin.username,
            password: this.formLogin.password,
            imageCode: this.formLogin.code
          }).then((res) => {
            // 修改用户登录状态
            this.$vp.modifyLoginState(true);
            //...
            const authorizeResources = parseAuthorizePaths(res.principal.admin.authorizeResources);
            this.$vp.rabcUpdateAuthorizeResourceAlias(authorizeResources.alias);
            const authorizeInterfaces = parseAuthorizeInterfaces(res.principal.admin.authorizeInterfaces);
            this.$vp.rabcUpdateAuthorizeInterfaces(authorizeInterfaces);
              //...
        }
      })
    }

这里的parseAuthorizePathsparseAuthorizeInterfaces的作用是解析后端返回的登录用户资源和接口列表,这个因人而异,就不贴了;

还需要注意的一点就是,this.$vp.modifyLoginState(true),是vue-viewplus插件登录身份控制模块所提供的一个接口,其可以为应用维护登录状态,比如在监控到后端返回会话超时时候自动将状态设置为false,更多请查看这里,这也是逻辑复用的一个好处了;

当然如果你只是想实现自己的权限控制模块,并不想抽象的这么简单,也可以硬编码到项目中;

这样我们就完成了第二个目标;

哦哦哦忘了写一下,我们怎么用这个指令了,补充一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<el-form v-access="{url: 'admin/search/*/*/*', method: 'POST'}" slot="search-inner-box" :inline="true" :model="searchForm" :rules="searchRules" ref="ruleSearchForm" class="demo-form-inline">
      //...
    </el-form>

上面是一个最简单的例子,即声明,如果要使用该检索功能,需要用户拥有:{url: 'admin/search/*/*/*', method: 'POST'这个接口权限;

另外指令的更多声明方式,请查看这里

要【实现发送请求前对待请求接口进行权限检查,如果用户不具有访问该后端接口的权限,则不发送请求,而是友好的提示用户】这个目标,我们的方案是:
  1. 获得登录用户的:
  • 被授权角色所拥有的资源列表(或资源)所对应的后端接口集合,这一步在实现第二个目标的时候已经完成,即在登录成功之后:this.$vp.rabcUpdateAuthorizeInterfaces(authorizeInterfaces);,这里只要复用即可
  1. 拦截请求,这里我们应用请求都是基于vue-viewplus的util-http.js 针对axios进行了二次封装的ajax模块来发送,它的好处是我80%的请求接口不用单独写错误处理代码,而是由改模块自动处理了,回到正题,我们怎么拦截请求,因为该ajax插件底层使用的是axios,对应的其提供了我们拦截请求的钩子https://github.com/Jiiiiiin/jiiiiiin-security#表结构和权限说明) 在具备以上条件之后我们好像就可以写代码了,嘿嘿:)

我们继续往上面的代码中添加逻辑,下面是代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const rbacModel = {
  //...
  install(Vue, {
    //...
    /**
     * [*] `$vp::onPathCheckFail(to, from, next)`
     * <p>
     * 发送ajax请求时权限检查失败时被回调
     */
    onAjaxReqCheckFail = null
  } = {}) {
    _onAjaxReqCheckFail = onAjaxReqCheckFail;
    this::_rbacAjaxCheck()
  }
};

还是在插件对象中,首先声明了所需配置的onAjaxReqCheckFail,其次调用_rbacAjaxCheck进行axios拦截声明:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 用于在发送ajax请求之前,对待请求的接口和当前集合进行匹配,如果匹配失败说明用户就没有请求权限,则直接不发送后台请求,减少后端不必要的资源浪费
 * @private
 */
const _rbacAjaxCheck = function() {
  this.getAjaxInstance().interceptors.request.use(
    (config) => {
      const { url, method } = config
      const statementAuth = []
      let isAllow
      if (_isRESTfulInterfaces) {
        const _method = _.toUpper(method)
        statementAuth.push({ url, method: _method });
        isAllow = _checkPermissionRESTful(statementAuth, _authorizeInterfaces)
        // TODO 因为拦截到的请求`{url: "admin/0/1/10", method: "GET"}` 没有找到类似java中org.springframework.util.AntPathMatcher;
        // 那样能匹配`{url: "admin/*/*/*", method: "GET"}`,的方法`temp = antPathMatcher.match(anInterface.getUrl(), reqURI)`
        // 故这个需求暂时没法实现 :)
        console.log('statementAuth', isAllow, statementAuth, _authorizeInterfaces)
      } else {
        isAllow = _checkPermission(statementAuth, _authorizeInterfaces)
      }
      if (isAllow) {
        return config;
      } else {
        if (_debug) {
          console.warn(`[v+] RBAC ajax权限检测不通过:用户无权发送请求【${method}-${url}`);
        }
        if (_.isFunction(_onAjaxReqCheckFail)) {
          this::_onAjaxReqCheckFail(config);
        } else {
          throw new Error('check_authorize_ajax_req_fail');
        }
      }
    },
    error => {
      return Promise.reject(error)
    }
  )
}

这里可能this.getAjaxInstance()不知道是什么,在调用_rbacAjaxCheck是我们指定了this,即this::_rbacAjaxCheck(),而这个this就是$vp对象,即vue-viewplus绑定到Vue实例的$vp属性;

其他的就很简单了,根据配置的_isRESTfulInterfaces属性看我们要校验的是RESTful接口还是普通接口,如果校验通过则返回axios所需请求config,如果失败则调用配置的_onAjaxReqCheckFail通知应用,让应用去处理权限失败的情况,一般也是弹出一个toast提示用户权限不足。

这样好像我们就完成了所有目标,哈哈哈。

写文章真是比敲代码累得多呀。

但是不幸的是我们并没有实现第三个目标,问题就在于,上面代码片段的TODO中所描述,我没有解决RESTful PathValue类型接口的权限对比,后端我用的库是通过:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 log.debug("内管权限校验开始:{} {} {}", admin.getUsername(), reqURI, reqMethod);
                for (Role role : roles) {
                    boolean temp;
                    for (Resource resource : role.getResources()) {
                        for (Interface anInterface : resource.getInterfaces()) {
                            temp = antPathMatcher.match(anInterface.getUrl(), reqURI) && reqMethod.equalsIgnoreCase(anInterface.getMethod());
                            if (temp) {
                                hasPermission = true;
                                break;
                            }
                        }
                    }
                }

org.springframework.util.AntPathMatcher提供的方法来完成的,但是js我没有找到合适的库来对比:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{url: "admin/*/*/*", method: "GET"} <> {url: "admin/0/1/10", method: "GET"}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-12-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 今日前端 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
SD-WAN入门,看这一篇就够了
2019年,SD-WAN的全球市场增长了70%,达到了23亿美元,并有望在未来五年中以每年45.5%的累计年增长率(CAGR)增长,到2024年实现220亿美元的市场规模。尽管SD-WAN发展得如火如荼,但是还是有很多人不清楚SD-WAN究竟是什么,以及它能够给我们带来什么,下面一起来看看吧!
SDNLAB
2020/10/27
4.2K0
SD-WAN入门,看这一篇就够了
SD-WAN菜鸟指南——SD-WAN概述
本章目录结构 ✔ 理解什么是软件定义广域网 ✔ 发现SD-WAN的需求 ✔ 探索SD-WAN特征 在本章,你会发现SD-WAN能够做什么,不能够实现什么。你同时也应该发现SD-WAN的特点。 SD-WAN的定义 SD-WAN提供了基于SDN数据中心的企业分支办公室在广域网中的解决方案。通过部署SDN和SD-WAN,可以虚拟化资源,加速合作伙伴的网络服务分发,更好的性能和改善可用性,自动网络部署和管理,已达到减少客户的花费。SDN在一般应用情况下,可以部署在任何网络环境中。SDN首先在大型的互联网公司
SDNLAB
2018/04/02
1.9K0
SD-WAN 大战 MPLS VPN 多年,赢了吗?
MPLS VPN在SD-WAN之前曾是企业内网的中流砥柱,稳定性和大型组网的首选之作,但放到当下市场却显得非常尴尬。
犀思云Syscloud
2023/11/08
5380
SD-WAN 大战 MPLS VPN 多年,赢了吗?
SD-WAN 已死?
乍一看这个标题大家一定很震惊,就在去年,这个标题还被用来形容SDN,而SD-WAN则作为SDN理念延续的佐证之一。 曾几何时,我们还在歌颂SD-WAN技术的带来,期待这个网络新宠能够帮助我们摆脱传统 MPLS 服务的束缚,但就在我们开始试着部署 SD-WAN 时,另一个更新、更安全、更快部署的技术出现了——SASE。SD-WAN是作为在SASE 世界中被抛弃和遗忘的网络技术存在,还是能够继续发挥重要作用?让我们来了解一下。 SD-WAN:诞生初期 SD-WAN 的诞生教会了企业如何从 MPLS 的时代进入
SDNLAB
2022/08/26
7380
SD-WAN 已死?
探讨国外9大领先的SD-WAN提供商的技术优势
SD-WAN技术有助于使广域网更加灵活和高效,从而降低网络管理成本。本文比较了9家领先的SD-WAN厂商的产品和服务的主要特性和功能、可用平台、目标用户以及定价和许可信息,厂商按字母顺序排列。
SDNLAB
2018/08/16
1.1K0
探讨国外9大领先的SD-WAN提供商的技术优势
“小贴士”助你构建SD-WAN
与容易部署的低成本宽带因特网(每月1美元/Mbps)相比,MPLS线路的成本相当高昂(每月200-400美元/Mbps),这引发了企业架构向软件定义的广域网转变。SD-WAN提供灵活性,以选择最佳传输路径,通过MPLS线路、公用互联网甚至无线LTE电路的混合来动态地导引流量。 接入传输的选择取决于多种因素,包括应用类型、传输特性、安全要求、QoS和网络丢失与延迟。当正确实施时,SD-WAN具有显著的优势:更快的服务部署、更高的灵活性、统一的管理和更好的应用性能等等。但是,虽然业界在过去一年中对
SDNLAB
2018/03/30
5490
“小贴士”助你构建SD-WAN
传统路由,SD-WAN给你提个醒
SD-WAN的优势很明显,能够实现带宽的经济应用、应用程序优先级和集中管理等优势,正在迅速重塑分支网络体系架构。由于诸多分支机构可以使用丰富的互联网带宽、优先访问关键应用程序以及轻松实现集中管理,SD
SDNLAB
2018/03/29
8190
传统路由,SD-WAN给你提个醒
什么是 SD-WAN软件定义的广域网,原创好文!
它是一个软件定义的广域网 (SDWAN),是一个从其硬件中抽象出来的网络,创建了一个虚拟化的 网络覆盖。
网络技术联盟站
2021/10/29
9720
什么是 SD-WAN软件定义的广域网,原创好文!
SD-WAN是否将终结IPsec VPN?
IPsec VPN是通过加密和隧道技术,在公共网络上创建虚拟专用网络的协议。它通过对数据包进行加密和封装,确保数据在传输过程中的安全性。IPsec VPN通常需要专门的硬件设备(如VPN网关)来处理加密和解密的过程,以及确保安全连接的建立。
犀思云Syscloud
2023/11/30
6350
SD-WAN是否将终结IPsec VPN?
详解:SD-WAN 架构终极指南
近年来,软件定义广域网 (SD-WAN) 被企业广泛采用,是分支机构连接到数据中心以及其他云应用的一种经济高效的方式。本文将围绕SD-WAN基础知识、工作原理、最佳实践和故障排除等多方面展开介绍,并对SD-WAN架构未来的发展进行分析。
SDNLAB
2021/07/27
1.6K0
详解:SD-WAN 架构终极指南
炙手可热的SD-WAN,运营起来很困难?丨科技云·视角
SD-WAN发展势头强劲,越来越多的企业用户纷纷着手开始部署SD-WAN,然而真正的SD-WAN技术与运营壁垒依然很高。
科技云报道
2022/04/14
4740
当企业广域网遇见SD-WAN–浅谈企业广域网和SD-WAN的几种接入模式
随着SDN技术的兴起,在WAN网络中应用SDN技术已经势不可挡,SD-WAN厂家纷纷推出自己的解决方案。纯粹的SD-WAN型解决方案部署集中控制器和分析软件,并将CPE改为基础的“白盒或x86平台”。
SDNLAB
2018/03/29
9520
当企业广域网遇见SD-WAN–浅谈企业广域网和SD-WAN的几种接入模式
SD-WAN虽好,高质量部署才是王道
广域网(WAN)几十年来一直是长途通信的基础。随着时间的推移,随着新的应用程序和功能的增加,复杂性和可管理性也随之增加,这会显著降低升级过程的速度,因为新功能通常需要添加或更改网络中的硬件 - 所有这些都需要在联机之前进行测试。
SDNLAB
2019/05/07
1.2K0
SD-WAN虽好,高质量部署才是王道
IT知识百科:SASE和SD-WAN
随着云计算、移动互联网和边缘计算的快速发展,企业对网络和安全的需求也在不断演变。两个重要的网络技术解决方案,即Secure Access Service Edge(SASE)和软件定义广域网(SD-WAN),应运而生。本文将对SASE和SD-WAN进行详细介绍,并比较它们的特点、功能和应用场景。
网络技术联盟站
2023/07/14
3450
IT知识百科:SASE和SD-WAN
SD-WAN和云专线浅见
随着相关技术的发展,SDN已在各行各业不同场景下得到快速应用。2017年,在广域网领域的“SD-WAN”和“云专线”词汇热度尤为突出,应SDNLAB小伙伴邀请,做一期SD-WAN和云专线的观点分享。本人结合SDN/NFV的从业经验,从有限的角度与大家探讨,欢迎大家指正。 一、维基定义SD-WAN “SD-WAN是软件定义广域网的缩写(Software Defined-WAN)。SD-WAN通过将网络硬件与其控制机制分离来简化WAN的管理和操作。 SD-WAN的关键应用是允许公司使用低
SDNLAB
2018/03/29
2.8K0
SD-WAN和云专线浅见
SDN之路从SD-WAN开始
SD-WAN就像一个智能的广域网加速器,它能够集中控制多个网络路径中的广域网流量,包括MPLS、V**网络、蜂窝数据服务LTE(如下图)。SD-WAN控制器使得多个链路看上去像一个逻辑链路,如同我们使用多年的以太网链路聚合。LTE等昂贵的链路,随着MPLS和V**网络的衰落得以保留。SD-WAN控制器通过采用优化WAN功能,来讲每个路径的效率最大化。 Figure 1. SD-WAN Deployment SD-WAN如何工作 SD-WAN控制器是基于每个路径中用户定义的策略、时延抖动
SDNLAB
2018/03/30
8440
SDN之路从SD-WAN开始
案例剖析 | 结合真实案例解读多分支企业选择SD-WAN网络重构的真相
在云计算和企业全球化的大背景下,数字化经济发展迅猛,加上后疫情时代的到来,移动办公、视频会议、虚拟系统等应用已经成为企业工作中不可或缺的工具,敏捷安全的网络是建立以上工作环境的前提条件,对多分支企业来说尤甚。
ICT网络运维闪星云
2021/10/15
8520
2000字带您了解什么是 SD-WAN,它是如何工作的?
软件定义的广域网 (SD-WAN) 被定义为一种虚拟 WAN 架构,它允许企业安全高效地将用户连接到应用程序。该技术解决方案为网络带来了无与伦比的敏捷性和成本节约。借助 SD-WAN,与企业传统上使用的托管 MPLS 服务相比,组织可以在更短的时间内以更低的成本交付响应更快、更可预测的应用程序。IT 变得更加敏捷,可以在几分钟内部署站点;利用任何可用的数据服务,例如 MPLS、专用互联网接入 (DIA)、宽带或无线;能够立即重新配置站点;并且更容易支持迁移到混合云。
网络技术联盟站
2023/03/02
8070
2000字带您了解什么是 SD-WAN,它是如何工作的?
SD-WAN的驱动者:云计算
软件定义广域网(SD-WAN)已经成为企业网络中最炙手可热的领域,它有望为企业提高敏捷性并节省成本。但是SD-WAN的关键驱动力是可以用于智能优化和保护与云计算的连接,包括将用户直接连接到数据中心,同
SDNLAB
2018/03/29
6300
SD-WAN的驱动者:云计算
SD-WAN:爱我就别想太多
SD-WAN 是基于软件定义网络的(SDN)技术,将数据的转发与控制分离,极大的简化网络的管理和操作,显著提升网络传输质量,使得广域网线路资源效率最大化。SD-WAN技术可以基于Internet、4G、和MPLS等线路,连接企业网络的分支机构和数据中心。
SDNLAB
2020/11/23
9290
SD-WAN:爱我就别想太多
推荐阅读
相关推荐
SD-WAN入门,看这一篇就够了
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验