Как правило, при создании SPA необходимо защищать определенные маршруты. Например, допустим, у нас есть маршрут к информационной панели, к которому могут обращаться только аутентифицированные пользователи, мы можем использовать промежуточное программное обеспечение (middleware) для аутентификации, чтобы гарантировать, что только аутентифицированные пользователи имеют доступ к маршруту на информационной панели.
В этом руководстве мы покажем, как реализовать использование промежуточного программного обеспечения (middleware) для приложения Vue с использованием Vue-Router.
Конвейер промежуточного программного обеспечения (middleware pipeline) — это стек различных промежуточных программ, запускаемых параллельно друг другу.
Используя наш гипотетический пример из вступительного абзаца, допустим, у нас есть другой маршрут в /dashboard /movies, который нам нужен только для подписавшихся пользователей. Мы уже знаем, что для доступа к маршруту информационной панели необходимо пройти аутентификацию. Как тогда мы можем защитить маршрут /dashboard/movies, чтобы гарантировать, что только аутентифицированные и подписанные пользователи имеют доступ к этому маршруту? Используя конвейер промежуточного программного обеспечения, мы можем связать несколько промежуточных программ вместе и обеспечить их параллельную работу.
Для начала мы будем использовать интерфейс командной строки Vue CLI для быстрого создания нового проекта Vue.
vue create vue-middleware-pipeline
После того, как был создан каталог проекта, перейдите во внутрь созданного каталога и выполните следующую команду из терминала:
npm i vue-router vuex
Vue-router — это официальный роутер для Vue.js
Vuex — библиотека управления состоянием для Vue
Наше приложение будет состоять из трех компонентов.
Login — Этот компонент будет отображаться пользователям, которые не прошли проверку подлинности.
Dashboard — Этот компонент будет отображаться для пользователей, которые вошли в систему.
Movies — Мы покажем этот компонент пользователям, которые вошли в систему и имеют активную подписку.
Давайте создадим эти компоненты. Перейдите в каталог src/components и создайте следующие файлы: Dashboard.vue, Login.vue и Movies.vue
Отредактируйте файл Login.vue:
<template> <div> <p>This is the Login component</p> </div> </template>
Отредактируйте файл Dashboard.vue:
<template> <div> <p>This is the Dashboard component for authenticated users</p> <router-view/> </div> </template>
Наконец, добавьте следующий код в файл Movies.vue:
<template> <div> <p>This is the Movies component for authenticated and subscribed users</p> </div> </template>
В отношении Vuex store — это просто контейнер для хранения состояния нашего приложения. Оно позволит нам определить, аутентифицирован ли пользователь, а также проверить, подписан ли пользователь или нет.
Внутри папки src создайте файл store.js и добавьте в него следующий код:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { user: { loggedIn: false, isSubscribed: false } }, getters: { auth(state) { return state.user } } })
Хранилище содержит объект пользователя user. Пользовательский объект содержит свойство loggedIn и isSubscribeed, которые помогают нам определить, вошел ли пользователь в систему и имеет ли активную подписку. Мы также определили геттер внутри store, который возвращает объект user.
Прежде чем мы создадим наши маршруты, давайте определим их и свяжем соответствующие промежуточные программы, которые будут подключены к каждому из этих маршрутов.
/login
будет доступен всем, кроме аутентифицированных пользователей. Когда аутентифицированные пользователи посещают этот маршрут, они должны быть перенаправлены на маршрут информационной панели. К этому маршруту будет прикреплено гостевое промежуточное ПО.
/dashboard
/ будет доступна только для аутентифицированных пользователей. Неаутентифицированные пользователи должны быть перенаправлены на маршрут /login, когда они посещают этот маршрут. Мы свяжем аутентификационное промежуточное ПО с этим маршрутом.
/dashboard/movies
будут доступны только для аутентифицированных и подписанных пользователей. Этот маршрут будет защищен промежуточным ПО isSubscribeed и auth.
Затем создайте папку router в каталоге src, а затем создайте файл router.js внутри этой папки. В несите следующий код в этот файл:
import Vue from 'vue' import Router from 'vue-router' import store from '../store' import Login from '../components/Login' import Dashboard from '../components/Dashboard' import Movies from '../components/Movies' Vue.use(Router) const router = new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { path: '/login', name: 'login', component: Login }, { path: '/dashboard', name: 'dashboard', component: Dashboard, children: [{ path: '/dashboard/movies', name: 'dashboard.movies', component: Movies } ], } ] }) export default router
Здесь мы создали новый экземпляр маршрутизатора, передавая пару параметров конфигурации, а также свойство routes, которое принимает все маршруты, которые мы определили ранее. На данный момент важно отметить, что ни один из этих маршрутов не защищен. Мы исправим это в ближайшее время.
Далее, давайте добавим router и наше хранилище store в экземпляр Vue. Отредактируйте файл src/main.js с кодом ниже:
import Vue from 'vue' import App from './App.vue' import router from './router/router' import store from './store' Vue.config.productionTip = false new Vue({ router, store, render: h => h(App), }).$mount('#app')
В каталоге src/router создайте папку middleware, а затем в этой папке создайте файлы guest.js, auth.js и IsSubscribeed.js. Добавьте следующий код в файл guest.js:
export default function guest ({ next, store }){ if(store.getters.auth.loggedIn){ return next({ name: 'dashboard' }) } return next() }
guest.js проверяет, прошел ли аутентификацию пользователь. Если он аутентифицирован, он перенаправляется на маршрут к dashboard.
Затем следующий код в файл auth.js :
export default function auth ({ next, store }){ if(!store.getters.auth.loggedIn){ return next({ name: 'login' }) } return next() }
auth.js используя хранилище, проверяет, аутентифицирован ли пользователь в данный момент. В зависимости от того, вошел ли пользователь в систему или нет, мы либо продолжим выполнение запроса, либо перенаправим его на страницу входа.
Внесите следующий код в файл isSubscribeed.js:
export default function isSubscribed ({ next, store }){ if(!store.getters.auth.isSubscribed){ return next({ name: 'dashboard' }) } return next() }
isSubscribeed.js аналогичен auth.js. Используя store, в нем проверяется, подписан ли пользователь. Если пользователь подписан, он может получить доступ к намеченному маршруту или же перенаправлен обратно на страницу панели мониторинга.
Теперь, когда мы создали все наши промежуточные программы, давайте использовать их для защиты наших маршрутов. Отредактируйте файл src/router/router.js с помощью следующего кода:
import Vue from 'vue' import Router from 'vue-router' import store from '../store' import Login from '../components/Login' import Dashboard from '../components/Dashboard' import Movies from '../components/Movies' import guest from './middleware/guest' import auth from './middleware/auth' import isSubscribed from './middleware/isSubscribed' Vue.use(Router) const router = new Router({ mode: 'history', base: process.env.BASE_URL, routes: [{ path: '/login', name: 'login', component: Login, meta: { middleware: [ guest ] } }, { path: '/dashboard', name: 'dashboard', component: Dashboard, meta: { middleware: [ auth ] }, children: [{ path: '/dashboard/movies', name: 'dashboard.movies', component: Movies, meta: { middleware: [ auth, isSubscribed ] } }], } ] }) export default router
Здесь мы импортировали все наше промежуточное программное обеспечение, а затем для каждого из маршрутов мы определили поле meta, содержащее массив middleware. Массив middleware содержит все промежуточное программное обеспечение, которое мы хотим связать с определенным маршрутом.
Навигационные средства защиты (navigation guards), предоставляемые Vue Router, являются единственной причиной, по которой мы можем использовать промежуточное ПО для защиты наших маршрутов. Эти средства навигации в основном используются для защиты маршрутов путем их перенаправления или отмены.
Один из этих guards — это глобальный before guard, который обычно представляет собой hook, вызываемый непосредственно перед срабатыванием маршрута. Чтобы зарегистрировать глобальный before guard, мы определяем метод beforeEach на экземпляре маршрутизатора.
const router = new Router({ ... }) router.beforeEach((to, from, next) => { //necessary logic to resolve the hook })
Метод beforeEach получает три аргумента:
to:
Это маршрут, к которому мы намерены получить доступ.
from:
Это маршрут, по которому мы сейчас идем.
next:
Это функция, вызываемая для разрешения хука.
Используя хук beforeEach, мы можем запустить наше промежуточное ПО.
const router = new Router({ ...}) router.beforeEach((to, from, next) => { if (!to.meta.middleware) { return next() } const middleware = to.meta.middleware const context = { to, from, next, store } return middleware[0]({ ...context }) })
Сначала мы проверяем, есть ли у текущего обрабатываемого маршрута поле meta, содержащее свойство middleware. Если свойство middleware найдено, мы присваиваем его переменной const middleware. Далее мы определяем объект context, который содержит все, что нам нужно что бы передать каждому middleware. Затем мы вызываем самое первое middleware в массиве middleware как функцию и передаем объект context.
Попробуйте посетить маршрут /dashboard, вы должны быть перенаправлены на страницу login. Это связано с тем, что свойство store.state.user.loggedIn в нашем /src/store.js имеет значение false. Измените свойство store.state.user.loggedIn на true, и теперь вы сможете получить доступ к маршруту /dashboard.
Наше middleware работает сейчас, но не совсем так, как мы этого хотим. Нашей целью было создать конвейер, в котором мы могли бы запускать несколько middleware по определенному маршруту.
return middleware[0]({ …context})
Обратите внимание на эту строку кода из приведенного выше блока кода, мы вызываем только первый кусок промежуточного программного обеспечения, переданный из массива middleware. Как тогда нам обеспечить, чтобы другие middleware, содержащиеся в массиве, тоже вызывались? Тут нам нужно конвейер (pipeline).
Перейдите в каталог src/router и затем создайте файл middlewarePipeline.js. Добавьте в него следующий код:
function middlewarePipeline (context, middleware, index) { const nextMiddleware = middleware[index] if(!nextMiddleware){ return context.next } return () => { const nextPipeline = middlewarePipeline( context, middleware, index + 1 ) nextMiddleware({ ...context, next: nextPipeline }) } } export default middlewarePipeline
MiddlewarePipeline принимает три аргумента:
context: это объект контекста, который мы создали ранее, чтобы его можно было передать каждому промежуточному программному обеспечению в стеке.
middleware: это сам массив middleware, определенный в поле route.
index: это индекс текущего middleware, запускаемого в массиве middleware.
const nextMiddleware = middleware[index] if(!nextMiddleware){ return context.next }
Здесь мы просто извлекаем middleware с помощью индекса, который был передан в функцию middlewarePipeline. Если middleware не найдено по этому индексу, запускается next.
return () => { const nextPipeline = middlewarePipeline( context, middleware, index + 1 ) nextMiddleware({ ...context, next: nextPipeline }) }
Тут мы вызываем nextMiddleware для запуска при передаче в контексте, а затем const nextPipeline. Важно отметить, что функция middlewarePipeline представляет собой рекурсивную функцию, которая будет вызывать себя для извлечения следующего middleware, которое будет выполняться в стеке, при увеличении индекса на 1.
Давайте воспользуемся нашим middleware. Отредактируйте файл src/router/router.js следующим образом:
import Vue from 'vue' import Router from 'vue-router' import store from '../store' import Login from '../components/Login' import Dashboard from '../components/Dashboard' import Movies from '../components/Movies' import guest from './middleware/guest' import auth from './middleware/auth' import isSubscribed from './middleware/isSubscribed' import middlewarePipeline from './middlewarePipeline' Vue.use(Router) const router = new Router({ mode: 'history', base: process.env.BASE_URL, routes: [{ path: '/login', name: 'login', component: Login, meta: { middleware: [ guest ] } }, { path: '/dashboard', name: 'dashboard', component: Dashboard, meta: { middleware: [ auth ] }, children: [{ path: '/dashboard/movies', name: 'dashboard.movies', component: Movies, meta: { middleware: [ auth, isSubscribed ] } }], } ] }) router.beforeEach((to, from, next) => { if (!to.meta.middleware) { return next() } const middleware = to.meta.middleware const context = { to, from, next, store } return middleware[0]({ ...context, next: middlewarePipeline(context, middleware, 1) }) }) export default router
Здесь мы используем middlewarePipeline для запуска последующих middleware, содержащихся в стеке.
return middleware[0]({ ...context, next: middlewarePipeline(context, middleware, 1) })
По поводу данного момента есть интересный комментарий
После вызова первого middleware с использованием функции middlewarePipeline также вызываются последующие middleware, содержащиеся в стеке, до тех пор, пока промежуточные программы не закончиться.
Если вы посещаете маршрут /dashboard/movies, вы должны быть перенаправлены на маршрут /dashboard. Это связано с тем, что user в настоящее время authenticated, но не имеет активной подписки. Установите для свойства store.state.user.isSubscribeed в нашем store на значение true. Теперь вы должны иметь доступ к /dashboard/movies.
Промежуточное ПО (Middlewares) — отличный способ защиты различных маршрутов в приложении. Это очень простая реализация того, как вы можете использовать несколько middlewares для защиты одного маршрута в вашем приложении Vue. Вы можете найти ссылку на репозиторий Github здесь.
Оригинальная статья: Dotun Jolaoso Understanding Vue middleware pipelines
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…
View Comments
А что если нет middleware?
Если middleware нет, вы можете это создать, в статье написано как. Если middleware не нужна то и не используйте его.
Я имел в виду что если его нет в роутере
Был невнимателен!
if (!to.meta.middleware) {
return next()
}
В статье ошибка. А конкретно в этом месте:
return middleware[0]({
...context,
next: middlewarePipeline(context, middleware, 1)
})
Вы переобозначаете функцию next из контекста какой-то сторонней, из-за чего невозможно выйти из какого-то Middleware не зайдя в следующий. Нужно сделать так:
В beforeEach:
return middleware[0]({
...context,
nextMiddleware: middlewarePipeline(context, middleware, 1)
})
И в middlewarePipeline:
return () => {
const nextPipeline = middlewarePipeline(
context, middleware, index + 1
)
nextMiddleware({ ...context, nextMiddleware: nextPipeline })
}
И описывать сами middleware вот так:
export default function auth ({ next, store, nextMiddleware }){
if(!store.getters.auth.loggedIn){
return next({
name: 'login'
})
}
return nextMiddleware()
}
Только тогда это будет работать как нужно. Т.е. при ошибке он вызовет функцию next vue-router и перейдет по ней, а если нет, то вызовет nextMiddleware. А то получалось так, что он вызывал next (в моем случае ту, которая nextMiddleware) и постоянно пробегался по всему массиву с middleware, пока не дойдет до конца и не вызовется
if(!nextMiddleware){
return context.next
}
Т.е. вообще не так, как должно было работать.
Поправьте статью, пожалуйста, а то не все читают комменты.
Спасибо за комментарий. Так как это перевод думаю вносить правки в код наверно не стоит. Но добавлю в статью примечание на этот комментарий.
спасибо большое, везде этот гайд с этой ошибкой, я то думаю чо я не так делаю, уже придумал извращенное решение, но твой коммент спас меня, спасибо))
На самом деле это ошибка и в то же время не ошибка, ибо если использовать next() только тогда, когда middleware пройдет все проверки и надо двигаться дальше, а если middleware проверку не прошел, то достаточно использовать функции router.push или взять redirect из контекста, чтобы куда нибудь перебросить пользователя. Но ваше решение тоже имеет право на жизнь, тут кому как удобнее. :)
В статье ошибка, но совсем не в том месте про которое вы пишите, а в auth.js (в оригинале к слову написано верно)
Огонь! Спасибо