Как клиентская часть взаимодействует с серверной частью для полного контроля разрешений RBAC

Vue.js

добавить модульисходный код


Связанные предыдущие статьиСпособ для интерфейсных приложений Vue реализовать контроль разрешений RBAC., Эта статья относительно ленива, ха-ха, спасибо за вашу поддержку.

СпасибоЛи ЯнЯ написал эту статью по приглашению человека.Я обычно много не пишу.Прошу простить меня за мои ошибки.

По рабочим причинам я искал управленческий шаблон для разработки внешнего интерфейса системы онлайн-банкинга и нашел его.d2-admin, выглядит очень аппетитно, а потом я подумал о том, чтобы сделать демонстрацию с разделенными интерфейсом и сервером, чтобы увидеть, соответствует ли шаблон d2-admin потребностям нашего проекта, тогда демонстрация управления разрешениями, я думаю, очень подходит для экспериментов, это все, что я сделалсистема разрешений jiiiiin-securityProject, ниже приведен скриншот некоторых функций:

Зачем нужна фронтенд реализация RBAC

Прежде чем мы поговорим о том, зачем нам нужно реализовать контроль разрешений на внешнем интерфейсе, вы должны понять суть того, чего мы хотим достичь.Вот два кратких вступления:

RBAC управление доступом на основе ролей(Английский:Role-based access control,RBAC), RBAC считает, что авторизация на самом деле является вопросом Кто, что, как. В модели RBAC кто, что и как составляют тройку прав доступа, то есть «кто выполняет операцию «как» над чем (каким)».

RBAC — это идея, которую можно реализовать на любом языке программирования, а его зрелая и простая идея управления становится все более популярной среди разработчиков.

Для получения дополнительной информации, пожалуйста, погуглите ее самостоятельно, если вы не знакомы с ней;

Я думаю, что интерфейс и серверная часть дополняют друг друга, поэтому необходимо хорошо поработать над контролем разрешений на интерфейсе.Если вы можете заранее понять правила распределения разрешений и структуру данных серверной части , вы можете лучше сотрудничать друг с другом.Конечно, если вы полностью проигнорируете разделение разрешений серверной части, жесткой.Выполнение двух вышеуказанных требований также достижимо, но трудно понять важность этого, не понимая Общая ситуация Поэтому рекомендуется, когда вы рассматриваете эту проблему (здесь относится к студентам, работающим с интерфейсом), вы должны взглянуть на RBAC Концепция классической структуры таблицы атрибутов и, следовательно, атрибутировать фоновые разрешения соответственно бизнес-правилам.

Обычно считается, что «управление разрешениями» является обязанностью серверной части, но с появлением приложений SPA за последние два года многие приложения были разработаны таким образом, чтобы разделить интерфейсную часть и внутреннюю часть, но чистый метод разработки внешнего интерфейса привел ко многим ранее проблемам, решаемым аппаратным обеспечением языка шаблонов внутреннего интерфейса, теперь необходимо перестроить колесо, и в настоящее время я думаю, что внешний интерфейсСотрудничатьФреймворк безопасности языка, соответствующий бэкенду, реализован в соответствии с его собственными бизнес-потребностями.Здесь мы поговорим о наших потребностях:

  1. Совершенствование нашего собственного плагина Vuevue-viewplusБизнес-модуль (этот плагин написан нами после года внутреннего использования, чтобы выделить в модули некоторые общие требования, необходимые для разработки приложений, что удобно для быстрой разработки приложений)
  2. Мы считаем, что если некоторые ненужные запросы могут быть перехвачены на внешнем интерфейсе в соответствии с правилами разрешений, настроенными на серверной части, ненужное потребление ресурсов серверной частью может быть уменьшено, а подсказки могут выполняться быстрее.обычный пользователь
  3. Нам нужно решить требование, чтобы меню и кнопки интерфейса управления были скрыты и отображены в соответствии с конфигурацией внутренних разрешений.
  4. Нам нужно удовлетворить потребность в динамической настройке доступности внешнего интерфейса в соответствии с конфигурацией внутренних разрешений.

Когда вышеупомянутые пункты 2, 3 и 4 не отделены от переднего и заднего концов, эти вещи обрабатываются задним классомязык шаблонов html(например, jsp в традиционной Java), например:

<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>

docs.spring.IO/spring-brush-ECU…

Цели

  • Мы надеемся, что при навигации по странице мы можем сначала судить, может ли пользователь получить доступ к странице в соответствии с разрешениями вошедшего в систему пользователя.
  • Реализовать частичные компоненты пользовательского интерфейса видимой страницы.удобство использования или видимостьконтроль, т.е. на основе пользовательскихv-accessДиректива, сравните, авторизован ли объявленный интерфейс или ресурс
  • Реализовать проверку разрешений на запрашиваемом интерфейсе перед отправкой запроса. Если у пользователя нет разрешения на доступ к внутреннему интерфейсу, запрос не будет отправлен, но пользователю будет предложено дружественное

Метод реализации

Для достижения цели [мы надеемся, что при навигации по странице мы сможем сначала оценить, может ли пользователь получить доступ к странице в соответствии с разрешениями вошедшего в систему пользователя], наше решение таково:
  1. Получить список путей доступных интерфейсных страниц для вошедшего в систему пользователя
  2. список публичных путей
  3. существуетrouterдля навигацииbeforeEachВ предварительном хуке оценивается, находится ли страница, запрошенная текущим пользователем, в двух вышеуказанных наборах. Если это так, она будет освобождена. Если нет, он уведомит вызывающую часть плагина и позволит ему обработать ошибку. сам по себе.

Вот реализация кода:

/**
 * 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Это не имеет значения, главное, чтобы вы знали, что установка экспортируемого объекта будет вызвана при входе в приложение, а также передано несколько необходимых параметров метода установки:

    • объект маршрута
    • Список путей общедоступных страниц для приложения
    • Функция обработки после сбоя проверки разрешения

    Таким образом, мы можем кэшировать список путей общего страницы приложения в функции инициализации, регистрируйте крюки маршрутизации и отслеживать изменения маршрутизации.

    Здесь я использую этот плагин для второй цели, используя его дляУправление статусом входа пользователя, см. ниже, почему я хочу использовать это состояние

  2. При мониторинге доступа к публичной странице,_rbacPathCheckФункция будет:

    • Сначала определите, является ли текущий пользовательсупер администратор, вы можете понять это как пользователь root в linux,Если да, отпустите прямо, это должно уменьшить накладные расходы, вызванные суждением.Конечно, если вам нужно добиться этого эффекта, вам нужно проверить информацию о пользователе в соответствии с информацией о пользователе, возвращаемой серверной частью после входа в систему.Роль, будь то супер администратор, если да, то назовите экспортированный файлrabcUpdateSuperAdminStatusметод, вот экземпляр страницыthis.$vp.rabcUpdateSuperAdminStatusметод(vue-viewplusПривяжите API, экспортируемый каждым модулем, к экземпляру страницы (то есть под атрибутом $vp vm):
    // 登录页面提交按钮绑定方法
    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 isSuperAdminStatus = parseUserRoleIsSuperAdminStatus(res.principal.admin.roles);
                this.$vp.toast('登录成功', {
                  type: 'success'
                });
                // 重定向对象不存在则返回顶层路径
                this.$router.replace(this.$route.query.redirect || '/')
              })
            } else {
              // 登录表单校验失败
              this.$message.error('表单校验失败')
            }
          })
        }
    
    • Если нет, проверьте, находится ли путь страницы, к которой нужно получить доступ, в списке путей общедоступных страниц приложения._publicPaths**, если да, то отпустите

      Предпосылка для принятия этого решения заключается в том, что приложению необходимо установить авторизованные внешние пути после успешного входа приложения.this.$vp.rabcUpdateAuthorizedPathsДайте плагин:

      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('表单校验失败')
              }
            })
          }
      

      Формат данных следующий:

      ["/mngauth/admin", "/index", "/mngauth"]
      

      И значение массива поддерживает регулярные выражения;

    • Если нет, проверьте, входит ли путь страницы, к которой нужно получить доступ, в набор путей маршрутизации, к которым у вошедшего в систему пользователя есть права доступа._authorizedPaths**, если он есть, то он будет разблокирован, если нет, то вся проверка завершится, и будет вынесено решение о том, что пользователь не имеет права доступа к странице, звоните_onPathCheckFailФункция обратного вызова для уведомления приложения, где приложение распечатает диалоговое окно с запросом пользователя.

    Поскольку наша цель состоит в том, чтобы абстрагировать весь бизнес, мы используем метод обратного вызова, чтобы позволить приложению фактически воспринимать и обрабатывать эту ситуацию;

    Это завершает нашу первую цель;

Для реализации [реализации частичных компонентов пользовательского интерфейса видимых страницудобство использования или видимостьконтроль, т.е. на основе пользовательскихv-accessДиректива по сравнению с авторизацией объявленного интерфейса или ресурса] Для этой цели наше решение:
  1. Войдите в систему пользователя:

    • Список ресурсов, принадлежащих авторизованным ролям, соответствующие псевдонимы ресурсов

      Формат данных аналогичен:

      ["MNG_USERMNG", "MNG_ROLEMNG"]
      
    • Набор внутренних интерфейсов, соответствующий списку ресурсов (или ресурсов), принадлежащих авторизованной роли.

      Формат данных аналогичен:

      ["admin/dels/*", "admin/search/*/*/*", "admin/*/*/*", "role/list/*", "admin/*"]
      

      Но по умолчанию ожидается формат RESTful:

      [{url: "admin/dels/*", method: "DELETE"}, ....]
      

      Конечно, регулярные выражения js также поддерживаются;

    Через два вышеуказанных набора данных авторизации (выберите один из двух) мы можем сравнить условные разрешения, заявленные пользователем в инструкции.

  2. Определите директиву Vue, названную здесьaccess, который должен иметь следующие характеристики:

    • Позволяет пользователям объявлять различные выражения разрешений, например, требуется ли для этой кнопки набор интерфейсов или псевдоним ресурса.
    • Позволяет пользователю контролировать, делать ли компоненты пользовательского интерфейса невидимыми или недоступными после того, как проверки разрешений не выполнены.

    Конечно, чтобы понять, как устроена приведенная выше структура данных, вы можете обратиться кСтруктура таблицы и описание разрешений

Мы продолжаем добавлять логику в приведенный выше код, следующая реализация кода:

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Цикл объявления, проанализируйте требуемую информацию о разрешении, объявленную соответствующим компонентом пользовательского интерфейса, и сравните ее со списком имеющихся ресурсов.Если сравнение не удалось, отобразите компонент пользовательского интерфейса соответствующим образом илиdisableработать:


/**
 * 推荐使用资源标识配置:`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
}

Перед использованием директивы нам также необходимо обратиться к настройкам списка разрешений, требуемых плагином:

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);
          	//...
        }
      })
    }

здесьparseAuthorizePathsа такжеparseAuthorizeInterfacesФункция состоит в том, чтобы анализировать пользовательские ресурсы входа в систему и список интерфейсов, возвращаемый бэкэндом.Это зависит от человека к человеку, поэтому он не будет опубликован;

Еще одна вещь, которую следует отметить, это то, чтоthis.$vp.modifyLoginState(true), представляет собой интерфейс, предоставляемый модулем управления идентификацией входа в систему плагина vue-viewplus, который может поддерживать статус входа в систему для приложения, например, при мониторинге серверной части для возврата тайм-аута сеанса.автоматическийустановить состояние наfalse, больше смотрите*здесь*, что также является преимуществом повторного использования логики;

Конечно, если вы просто хотите реализовать свой собственный модуль контроля разрешений, вы не хотите абстрагироваться таким образом.Простой, который также может быть жестко закодирован в проекте;

Это завершает нашу вторую цель;

Ой ой ой забыл написать, как мы используем эту команду, добавляем:

<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);, просто используйте его здесь
  2. Запросы на перехват, здесь все запросы наших приложений основаны на vue-viewplusutil-http.js — это модуль ajax со вторичной инкапсуляцией для axios.для отправки, его преимущество в том, что 80% моих интерфейсов запросов не нужно писать код обработки ошибок отдельно, а меняя модульавтоматическийОбработано, вернемся к теме, как мы перехватываем запрос, ведь нижний слой ajax-плагинаaxios, соответственно, он дает нам хук для перехвата запросов https://github.com/Jiiiiiiin/jiiiiin-security#Структура таблицы и описание разрешений)

    После выполнения вышеуказанных условий мы, кажется, можем писать код, хе-хе :)

Продолжаем добавлять логику в вышеприведенный код, вот реализация кода:

const rbacModel = {
  //...
  install(Vue, {
    //...
    /**
     * [*] `$vp::onPathCheckFail(to, from, next)`
     * <p>
     * 发送ajax请求时权限检查失败时被回调
     */
    onAjaxReqCheckFail = null
  } = {}) {
    _onAjaxReqCheckFail = onAjaxReqCheckFail;
    this::_rbacAjaxCheck()
  }
};

Или в объекте плагина сначала объявить требуемую конфигурациюonAjaxReqCheckFail, с последующим вызовом_rbacAjaxCheckСделайте оператор перехвата axios:


/**
 * 用于在发送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::_rbacAjaxCheck(), и этоthisто есть$vpОбъект, то есть vue-viewplus, привязан к экземпляру Vue.$vpАтрибуты;

Другие очень простые, по конфигурации_isRESTfulInterfacesАтрибут зависит от того, хотим ли мы проверить интерфейс RESTful или обычный интерфейс, если проверка проходит, она возвращает требуемый axios конфиг, а если не проходит, то вызывается настроенный конфиг._onAjaxReqCheckFailУведомить приложение, позволить приложению обработать отказ разрешения, обычно всплывающее окноtoastВыдается сообщение о недостаточных правах пользователя.

Похоже, мы достигли всех наших целей, хахаха.

Писать статьи гораздо утомительнее, чем набирать код.

Но, к сожалению, мы не достигли третьей цели. Проблема в том, что, как описано в TODO приведенного выше фрагмента кода, я не решил сравнение разрешений интерфейса типа RESTful PathValue. Я использовал внутреннюю библиотеку через:

 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 для сравнения:

{url: "admin/*/*/*", method: "GET"} <> {url: "admin/0/1/10", method: "GET"}

Такие два объекта, так что наберитесь терпения, чтобы увидеть здесь друзей, если вы решите эту проблему, свяжитесь со мной, спасибо.

Спасибо за ваше терпение увидеть здесь, если вы найдете это полезным, пожалуйста, помогите поддержать мои два проекта:

vue-viewplus

jiiiiiin-security

Двигай ручонками, проси звезду, эй, хахаха.