Практика управления разрешениями одностраничного приложения vuejs

задняя часть внешний интерфейс Vue.js React.js

Оригинальный текст был опубликован на http://blog.ahui.me/posts/2018-03-26/permission-control-of-vuejs/

Среди многих приложений на стороне B, простые, как фон управления небольшим предприятием или крупномасштабная CMS, система CRM и управление разрешениями, являются главными приоритетами.Большинство прошлых веб-приложений использовали шаблоны на стороне сервера + сервер- в режиме боковой маршрутизации управление разрешениями естественным образом контролируется и фильтруется сервером.Однако в условиях разделения клиентской и серверной части, если принят режим разработки одностраничных приложений, клиентская часть неизбежно будет сотрудничать с сервером для управляйте разрешениями вместе. Взяв за пример разработку одностраничных приложений с vuejs, приводятся некоторые попытки, и я надеюсь дать вам некоторые идеи. Обратите внимание, что разделение интерфейса и сервера с использованием nodejs в качестве среднего уровня не входит в предмет данной статьи.

Цель

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

Общее управление правами разделено на следующие части.

  • Права на использование приложений
  • Разрешения на уровне страницы
  • Разрешения на уровне модуля
  • Разрешения на уровне интерфейса

Далее приведенные выше части будут объяснены одна за другой. Полный код примера размещен вgithub-funkyLover/vue-permission-control-demoначальство.

Права на использование приложения — управление статусом входа и хранение

первый应用使用权На самом деле, это просто оценка состояния входа в систему. Во многих приложениях на стороне C возможность использования дополнительных функций после входа в систему также может в определенной степени учитываться как часть управления разрешениями. В приложениях на стороне B это обычно показывает что вы не можете использовать его без входа в систему. (Конечно, вы также можете использовать такие функции, как восстановление пароля).

В прошлом состояние входа в систему обычно управлялось сеансом + файлом cookie/токеном. Пользователь приносил файл cookie/токен при открытии веб-страницы, которая оценивалась внутренней логикой и перенаправлялась. В режиме SPA переход на страницу был выполнен интерфейсной маршрутизацией. Контролируемая оценка статуса пользователя должна быть активно отправлена ​​интерфейсной частью один раз.自动登录запрос и переход в соответствии с возвращенным результатом.

это自动登录Логику можно копать глубже, чтобы сделать различные реализации.Например, после успешного входа в систему информация о пользователе шифруется и распределяется между несколькими вкладками через локальное хранилище, так что при повторном открытии вкладки нет необходимости повторно -открой еще раз.自动登录, Вот простейшая реализация для объяснения, основной процесс выглядит следующим образом:

  1. Пользователь запрашивает ресурсы страницы
  2. Проверьте, является ли локальный файл cookie / localstorage токеном
  3. Если токена нет, независимо от того, какой маршрут запрашивает пользователь, он всегда будет переходить на маршрут входа.
  4. Если токен проверен, сначала запросите自动登录интерфейс, в соответствии с возвращенным результатом, чтобы определить, следует ли вводить маршрут, запрошенный пользователем, или переходить к маршруту входа

Что касается суждения о статусе пользователя, как правило, оно должно основываться на进入login路由(включая такие маршруты, как забытые пароли) и进入其他路由Чтобы принять решение, исходя из vuejs@2.x, вы можете оценить статус пользователя и переключить маршрут на хуке маршрутизатора beforeEach.Некоторые коды приведены ниже:

const routes = [
  {
    path: '/',
    component: Layout,
    children: [
      {
        path: '',
        name: 'Dashboard',
        component: Dashboard
      }, {
        path: 'page1',
        name: 'Page1',
        component: Page1
      }, {
        path: 'page2',
        name: 'Page2',
        component: Page2
      }
    ]
  }, {
    path: '/login',
    name: 'Login',
    component: Login
  }
]

const router = new Router({
  routes,
  mode: 'history'
  // 其他配置
})

router.beforeEach((to, from, next) => {
  if (to.name === 'Login') {
    // 当进入路由为login时,判断是否已经登录
    if (store.getters.user.isLogin) {
      // 如果已经登录,则进入功能页面
      return next('/')
    } else {
      return next()
    }
  } else {
    if (store.getters.user.isLogin) {
      return next()
    } else {
      // 如果没有登录,则进入login路由
      return next('/login')
    }
  }
})

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

// Login.vue
async mounted () {
  var token = Cookie.get('vue-login-token')
  if (token) {
    var { data } = await axios.post('/api/loginByToken', {
      token: token
    })
    if (data.ok) {
      this[LOGIN]()
      Cookie.set('vue-login-token', data.token)
      this.$router.push('/')
    } else {
      // 登录失败逻辑
    }
  }
},
methods: {
  ...mapMutations([
    LOGIN
  ]),
  async login () {
    var { data } = await axios.post('/api/login', {
      username: this.username,
      password: this.password
    })
    if (data.ok) {
      this[LOGIN]()
      Cookie.set('vue-login-token', data.token)
      this.$router.push('/')
    } else {
      // 登录错误逻辑
    }
  }
}

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

Разрешения на уровне страницы — создание объектов маршрутизатора на основе разрешений

Здесь вы можете использоватьЭксклюзивная защита vue-router/routingДля обработки Основная идея состоит в том, чтобы установить хук-функцию beforeEnter в каждом маршруте, который должен проверять разрешения, и судить о разрешениях пользователя в нем.


const routes = [
  {
    path: '/',
    component: Layout,
    children: [
      {
        path: '',
        name: 'Dashboard',
        component: Dashboard
      }, {
        path: 'page1',
        name: 'Page1',
        component: Page1,
        beforeEnter: (to, from, next) => {
          // 这里检查权限并进行跳转
          next()
        }
      }, {
        path: 'page2',
        name: 'Page2',
        component: Page2,
        beforeEnter: (to, from, next) => {
          // 这里检查权限并进行跳转
          next()
        }
      }
    ]
  }, {
    path: '/login',
    name: 'Login',
    component: Login
  }
]

Приведенного выше кода достаточно, чтобы выполнить требования, а затем сотрудничать сvue-router/ленивая загрузка маршрутаТакже возможно реализовать, что ресурсы соответствующих компонентов страницы не будут загружены для маршрутов без разрешений, однако в приведенной выше реализации все еще есть некоторые проблемы.

  1. Когда права доступа к странице будут достаточно подробными, конфигурация маршрутизатора станет больше и сложнее в обслуживании.
  2. Всякий раз, когда правила разрешений страницы обновляются в фоновом режиме, логика оценки внешнего интерфейса также должна быть изменена, а это означает, что внешний и внутренний интерфейсы должны совместно поддерживать набор разрешений на уровне страницы.

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

// Login.vue
async mounted () {
  var token = Cookie.get('vue-login-token')
  if (token) {
    var { data } = await axios.post('/api/loginByToken', {
      token: token
    })
    if (data.ok) {
      this[LOGIN]()
      Cookie.set('vue-login-token', data.token)
      // 这里调用更新router的方法
      this.updateRouter(data.routes)
    }
  }
},
// ...
methods: {
  async updateRouter (routes) {
    // routes是后台返回来的路由信息
    const routers = [
      {
        path: '/',
        component: Layout,
        children: [
          {
            path: '',
            name: 'Dashboard',
            component: Dashboard
          }
        ]
      }
    ]
    routes.forEach(r => {
      routers[0].children.push({
        name: r.name,
        path: r.path,
        component: () => routesMap[r.component]
      })
    })
    this.$router.addRoutes(routers)
    this.$router.push('/')
  }
}

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

  1. Каждый раз, когда вы открываете новую вкладку (маршрутизация без входа в систему)自动登录и перепродлить роутер
  2. Каждый раз, когда вы открываете новую вкладку, она по-прежнему будет переходить на/маршрутизация, даже если вновь открытый URL-адрес/page1

Решение состоит в том, чтобы хранить информацию о входе пользователя и информацию о маршрутизации в локальном хранилище и напрямую генерировать объекты маршрутизатора с помощью информации, хранящейся в локальном хранилище, при открытии новой вкладки.store.jsа такжеvuex-shared-mutationsКласс плагинов может в некоторой степени упростить эту часть логики, и здесь он обсуждаться не будет.

Разрешения уровня модуля — разрешения компонента

Разрешения на уровне модуля легко понять. На самом деле это компоненты с оценкой разрешений. Очень просто и легко понять компоненты, которым необходимо фильтровать разрешения с помощью компонентов более высокого порядка в React. См. следующий пример.

const withAuth = (Comp, auth) => {
  return class AuthComponent extends Component {
    constructor(props) {
      super(props);
      this.checkAuth = this.checkAuth.bind(this)
    }

    checkAuth () {
      const auths = this.props;
      return auths.indexOf(auth) !== -1;
    }

    render () {
      if (this.checkAuth()) {
        <Comp { ...this.props }/>
      } else {
        return null
      }
    }
  }
}

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

В vuejs вы можете использовать функцию рендеринга для достижения

// Auth.vue
import { mapGetters } from 'vuex'

export default {
  name: 'Auth-Comp',
  render (h) {
    if (this.auths.indexOf(this.auth) !== -1) {
      return this.$slots.default
    } else {
      return null
    }
  },
  props: {
    auth: String
  },
  computed: {
    ...mapGetters(['auths'])
  }
}
// 使用
<Auth auth="canShowHello">
  <Hello></Hello>
</Auth>

Функция рендеринга в vuejs предоставляет полные возможности программирования и даже может использовать синтаксис jsx в функции рендеринга, чтобы получить опыт разработки, близкий к React.документация vuejs/функции рендеринга и jsx.

Разрешения на уровне интерфейса

Разрешения на уровне интерфейса, как правило, не связаны с библиотекой пользовательского интерфейса. Вот краткое введение в то, как с ними работать.

  1. Во-первых, получите разрешение интерфейса API, к которому текущему пользователю разрешен доступ из бэкэнда.
  2. Настройте перехватчик внешней библиотеки запросов ajax (например, axios) в соответствии с возвращенным результатом.
  3. Оценка разрешений в перехватчике и подсказка пользователям в соответствии с их потребностями
axios.interceptors.request.use((config) => {
  // 这里进行权限判断
  if (/* 没有权限 */) {
    return Promise.reject('no auth')
  } else {
    return config
  }
}, err => {
  return Promise.reject(err)
})

На самом деле, я лично считаю, что фронтенду не обязательно судить о разрешениях запрашиваемого апи, ведь интерфейс не похож на маршрут, и маршрутом теперь управляет фронтенд, а интерфейс необходимо пройти проверку сервера в конце.Его можно добавить по мере необходимости.

постскриптум

Это грязно, как работающая учетная запись, полный пример кода находится вgithub-funkyLover/vue-permission-control-demo, Если у вас есть какие-либо вопросы или комментарии, пожалуйста, оставьте комментарий, я буду смиренно преподавать.