Аутентификация Vue и обработка маршрута с помощью Vue-router

Аутентификация Vue и обработка маршрута с помощью Vue-router

От автора: Vue — это прогрессивный фреймворк Javascript, который упрощает создание приложений с интерфейсом. В сочетании с vue-router мы можем создавать высокопроизводительные приложения с полными динамическими маршрутами. Vue router — эффективный инструмент и может легко обрабатывать аутентификацию в нашем Vue-приложении. В этом уроке мы рассмотрим использование vue-router для проверки подлинности и контроля доступа для разных частей нашего приложения.

Начало работы

Для начала установите Vue cli и создайте с его помощью приложение:

$ npm install -g @vue/cli
$ npm install -g @vue/cli-init
$ vue init webpack vue-router-auth

Следуйте инструкциям по установке и завершите установку этого приложения. Если вы не уверены в опции, просто нажмите клавишу возврата (клавиша enter), чтобы продолжить настройку по умолчанию. Когда вас попросят установить vue-router, примите этот параметр, потому что нам нужен vue-router для этого приложения.

Установка сервера Node.js

Затем мы настроим сервер Node.js, который будет обрабатывать аутентификацию для нас. Для нашего сервера Node.js мы будем использовать SQLite в качестве базы данных по выбору. Для установки драйвера SQLite выполните следующую команду:

$ npm install --save sqlite3

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

$ npm install --save bcrypt

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

$ npm install jsonwebtoken –save

Чтобы прочитать данные json, которые мы отправим на наш сервер, нам нужен body-parser. Для его установки выполните следующую команду:

$ npm install --save body-parser

Когда все будет готово, создадим простой сервер nodejs, который будет обрабатывать аутентификацию пользователя. Создайте новый каталог с именем server. Здесь мы будем хранить все, что мы будем использовать для создания нашего узла. В каталоге сервера создайте файл и сохраните его как app.js. Добавьте к нему следующее:

"use strict";
const express = require('express');
const cors = require('cors');
const DB = require('./db');
const config = require('./config');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser'); const db = new DB("sqlitedb")
const app = express();
const router = express.Router(); router.use(bodyParser.urlencoded({ extended: false }));
router.use(bodyParser.json()); // CORS middleware
const allowCrossDomain = function(req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', '*'); res.header('Access-Control-Allow-Headers', '*'); next();
} app.use(allowCrossDomain) router.post('/register', function(req, res) { db.insert([ req.body.name, req.body.email, bcrypt.hashSync(req.body.password, 8) ], function (err) { if (err) return res.status(500).send("There was a problem registering the user.") db.selectByEmail(req.body.email, (err,user) => { if (err) return res.status(500).send("There was a problem getting user") let token = jwt.sign({ id: user.id }, config.secret, {expiresIn: 86400 // expires in 24 hours }); res.status(200).send({ auth: true, token: token, user: user }); }); }); }); router.post('/register-admin', function(req, res) { db.insertAdmin([ req.body.name, req.body.email, bcrypt.hashSync(req.body.password, 8), 1 ], function (err) { if (err) return res.status(500).send("There was a problem registering the user.") db.selectByEmail(req.body.email, (err,user) => { if (err) return res.status(500).send("There was a problem getting user") let token = jwt.sign({ id: user.id }, config.secret, { expiresIn: 86400 // expires in 24 hours }); res.status(200).send({ auth: true, token: token, user: user }); }); }); }); router.get('/me', function(req, res) { let token = req.headers['x-access-token']; if (!token) return res.status(401).send({ auth: false, message: 'No token provided.' }); jwt.verify(token, config.secret, function(err, decoded) { if (err) return res.status(500).send({ auth: false, message: 'Failed to authenticate token.' }); res.status(200).send(decoded); });
}); router.post('/login', (req, res) => { db.selectByEmail(req.body.email, (err, user) => { if (err) return res.status(500).send('Error on the server.'); if (!user) return res.status(404).send('No user found.'); let passwordIsValid = bcrypt.compareSync(req.body.password, user.user_pass); if (!passwordIsValid) return res.status(401).send({ auth: false, token: null }); let token = jwt.sign({ id: user.id }, config.secret, { expiresIn: 86400 // expires in 24 hours }); res.status(200).send({ auth: true, token: token, user: user }); });
}) app.use(router) let port = process.env.PORT || 3000; let server = app.listen(port, function() { console.log('Express server listening on port ' + port)
});

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

Затем мы продолжили определять разные маршруты для нашего сервера и то, что должен делать наш сервер, когда пользователь делает запрос на маршрут. Наконец, мы создали сервер на port: 3000 или любой динамически сгенерированный порт нашей системой (heroku генерирует динамические порты).

Затем создайте еще один файл config.js в том же каталоге и добавьте к нему следующее:

module.exports = { 'secret': 'supersecret' }; 

Наконец, создайте еще один файл db.js и добавьте к нему следующее:

"use strict";
const sqlite3 = require('sqlite3').verbose(); class Db { constructor(file) { this.db = new sqlite3.Database(file); this.createTable() } createTable() { const sql = ` CREATE TABLE IF NOT EXISTS user ( id integer PRIMARY KEY, name text, email text UNIQUE, user_pass text)` return this.db.run(sql); } selectByEmail(email, callback) { return this.db.get( `SELECT * FROM user WHERE email = ?`, [email],function(err,row){ callback(err,row) }) } insertAdmin(user, callback) { return this.db.run( 'INSERT INTO user (name,email,user_pass,is_admin) VALUES (?,?,?,?)', user, (err) => { callback(err) }) } selectAll(callback) { return this.db.all(`SELECT * FROM user`, function(err,rows){ callback(err,rows) }) } insert(user, callback) { return this.db.run( 'INSERT INTO user (name,email,user_pass) VALUES (?,?,?)', user, (err) => { callback(err) }) }
} module.exports = Db 

Мы создали класс для нашей базы данных, чтобы абстрагировать основные функции, которые нам нужны. Вы можете использовать более общие и многоразовые методы здесь для операций с базой данных и, вероятно, использовать promise для большей эффективности. Это позволит вам создать репозиторий, который вы можете использовать со всеми другими определяемыми вами классами (особенно если ваше приложение использует архитектуру MVC и имеет контроллеры).

Все сделано и выглядит хорошо, давайте сделаем vue-приложение.

Обновление файла Vue-router

Файл vue-router можно найти в ./src/router/. В файле index.js мы определим все маршруты, которые нужны нашему приложению. Это отличается от того, что мы сделали с нашим сервером, и его не следует путать.

Откройте файл и добавьте следующее:

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Login from '@/components/Login'
import Register from '@/components/Register'
import UserBoard from '@/components/UserBoard'
import Admin from '@/components/Admin' Vue.use(Router) let router = new Router({ mode: 'history', routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld }, { path: '/login', name: 'login', component: Login, meta: { guest: true } }, { path: '/register', name: 'register', component: Register, meta: { guest: true } }, { path: '/dashboard', name: 'userboard', component: UserBoard, meta: { requiresAuth: true } }, { path: '/admin', name: 'admin', component: Admin, meta: { requiresAuth: true, is_admin : true } }, ]
}) router.beforeEach((to, from, next) => { if(to.matched.some(record => record.meta.requiresAuth)) { if (localStorage.getItem('jwt') == null) { next({ path: '/login', params: { nextUrl: to.fullPath } }) } else { let user = JSON.parse(localStorage.getItem('user')) if(to.matched.some(record => record.meta.is_admin)) { if(user.is_admin == 1){ next() } else{ next({ name: 'userboard'}) } }else { next() } } } else if(to.matched.some(record => record.meta.guest)) { if(localStorage.getItem('jwt') == null){ next() } else{ next({ name: 'userboard'}) } }else { next() }
}) export default router 

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

Если вы посмотрите на определения маршрута, вы заметите, что некоторые маршруты имеют свойство meta. Это свойство meta позволяет нам определить дополнительные условия для проверки перед тем, как разрешить пользователю видеть страницу / монтировать компонент или перенаправлять пользователя на другую страницу. В этом определении маршрутизатора мы использовали свойство meta со значениями is_admin, is_admin и guest.

У маршрутизатора Vue есть метод beforeEach, который вызывается перед обработкой каждого маршрута. Здесь мы можем определить наше условие проверки и ограничить доступ пользователей. Метод принимает три параметра — to, from и next. To – куда пользователь хочет перейти, from – откуда пользователь приходит, и next — функция обратного вызова, которая продолжает обработку запроса пользователя. Наша проверка находится на объекте.

Мы проверяем несколько вещей:

если маршрут requiresAuth, проверяем токен jwt, показывающий, что пользователь вошел в систему.

если маршрут requiresAuth и предназначен только для пользователей admin, проверяем auth и проверяем, является ли пользователь администратором

если маршрут требует guest, проверяем, зарегистрирован ли пользователь

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

ВАЖНО: всегда убедитесь, что вы вызываете next() в конце каждого условия, которое вы проверяете. Это делается для предотвращения сбоя вашего приложения в случае, если есть условие, которое вы забыли проверить.

Определение компонентов

Чтобы проверить, что мы создали, давайте определим несколько компонентов. В каталоге ./src/components/ откройте файл HelloWorld.vue и добавьте следующее:

<template> <div class="hello"> <h1>This is homepage</h1> <h2>{{msg}}</h2> </div>
</template> <script> export default { data () { return { msg: 'Hello World!' } } }
</script> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped> h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; }
</style> 

Создайте новый файл Login.vue в том же каталоге и добавьте следующее:

<template> <div> <h4>Login</h4> <form> <label for="email" >E-Mail Address</label> <div> <input id="email" type="email" v-model="email" required autofocus> </div> <div> <label for="password" >Password</label> <div> <input id="password" type="password" v-model="password" required> </div> </div> <div> <button type="submit" @click="handleSubmit"> Login </button> </div> </form> </div>
</template>
<script> export default { data(){ return { email : "", password : "" } }, methods : { handleSubmit(e){ e.preventDefault() if (this.password.length > 0) { this.$http.post('http://localhost:3000/login', { email: this.email, password: this.password }) .then(response => { let is_admin = response.data.user.is_admin localStorage.setItem('user',JSON.stringify(response.data.user)) localStorage.setItem('jwt',response.data.token) if (localStorage.getItem('jwt') != null){ this.$emit('loggedIn') if(this.$route.params.nextUrl != null){ this.$router.push(this.$route.params.nextUrl) } else { if(is_admin== 1){ this.$router.push('admin') } else { this.$router.push('dashboard') } } } }) .catch(function (error) { console.error(error.response); }); } } } }
</script> 

В файле Login.vue мы определили компонент входа. Мы определили метод обработки представления пользователя и входа пользователя в систему. Затем создайте файл Register.vue и добавьте в него следующее:

<template> <div> <h4>Register</h4> <form> <label for="name">Name</label> <div> <input id="name" type="text" v-model="name" required autofocus> </div> <label for="email" >E-Mail Address</label> <div> <input id="email" type="email" v-model="email" required> </div> <label for="password">Password</label> <div> <input id="password" type="password" v-model="password" required> </div> <label for="password-confirm">Confirm Password</label> <div> <input id="password-confirm" type="password" v-model="password_confirmation" required> </div> <label for="password-confirm">Is this an administrator account?</label> <div> <select v-model="is_admin"> <option value=1>Yes</option> <option value=0>No</option> </select> </div> <div> <button type="submit" @click="handleSubmit"> Register </button> </div> </form> </div>
</template> <script> export default { props : ["nextUrl"], data(){ return { name : "", email : "", password : "", password_confirmation : "", is_admin : null } }, methods : { handleSubmit(e) { e.preventDefault() if (this.password === this.password_confirmation && this.password.length > 0) { let url = "http://localhost:3000/register" if(this.is_admin != null || this.is_admin == 1) url = "http://localhost:3000/register-admin" this.$http.post(url, { name: this.name, email: this.email, password: this.password, is_admin: this.is_admin }) .then(response => { localStorage.setItem('user',JSON.stringify(response.data.user)) localStorage.setItem('jwt',response.data.token) if (localStorage.getItem('jwt') != null){ this.$emit('loggedIn') if(this.$route.params.nextUrl != null){ this.$router.push(this.$route.params.nextUrl) } else{ this.$router.push('/') } } }) .catch(error => { console.error(error); }); } else { this.password = "" this.passwordConfirm = "" return alert("Passwords do not match") } } } }
</script> 

Код похож на структуру файла Login.vue. Он создает компонент регистра и сопровождающий метод для обработки заявки пользователя на регистрационную форму. Теперь создайте файл Admin.vue и добавьте следующее:

<template> <div class="hello"> <h1>Welcome to administrator page</h1> <h2>{{msg}}</h2> </div>
</template> <script> export default { data () { return { msg: 'The superheros' } } }
</script>
<style scoped> h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; }
</style> 

Это компонент, который мы установим, когда пользователь посещает страницу администратора. Наконец, создайте файл UserBoard.vue и добавьте следующее:

<template> <div class="hello"> <h1>Welcome to regular users page</h1> <h2>{{msg}}</h2> </div>
</template> <script> export default { data () { return { msg: 'The commoners' } } }
</script> <!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped> h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; }
</style> 

Это файл, который мы увидим, когда пользователь зайдет на страницу панели мониторинга. По компонентам все.

Глобальная установка Axios

Для всех наших запросов на сервер мы будем использовать axios. Axios — это клиент HTTP на основе promise для браузера и node.js. Для установки axios выполните следующую команду:

$ npm install --save axios

Чтобы сделать его доступным для всех наших компонентов, откройте файл ./src/main.js и добавьте следующее:

import Vue from 'vue'
import App from './App'
import router from './router'
import Axios from 'axios' Vue.prototype.$http = Axios; Vue.config.productionTip = false new Vue({ el: '#app', router, components: { App }, template: '<App/>'
}) 

Определив Vue.prototype.$http = Axios, мы модифицировали vue-движок и добавили аксиомы. Теперь мы можем использовать axios во всех наших компонентах this.$http.

Запуск приложения

Теперь, когда мы закончили работу с приложением, нам нужно собрать все наши файлы и запустить его. Поскольку у нас есть узел js-сервера вместе с нашим vue-приложением, мы будем использовать оба из них, чтобы наше приложение работало.

Давайте добавим скрипт, который поможет нам запустить наш node server. Откройте файл package.json и добавьте следующее:

[...]
"scripts": { "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", "server": "node server/app", "build": "node build/build.js" },
[...] 

Мы добавили server скрипт, чтобы помочь нам запустить node server. Теперь запустите следующую команду для запуска сервера:

$ npm run server

Должно отобразиться примерно следующее:

Затем откройте еще одну консоль и запустите приложение vue:

$ npm run dev

Команда соберем все файлы и запустит приложение. Можете открыть ссылку, по ней вы увидите приложение.

Заключение

В этом руководстве мы узнали, как использовать vue-router для определения проверок на наших маршрутах и предотвращения доступа пользователей к определенным маршрутам. Мы также узнали, как перенаправить пользователей в разные части нашего приложения на основе состояния аутентификации. Мы также создали мини-сервер с Node.js для обработки аутентификации пользователей.

То, что мы сделали, является примером того, как управление доступом проектируется в таких системах, как Laravel. Вы можете посмотреть vue-router и посмотреть, какие еще интересные вещи вы можете сделать с этим.

Автор: Chris Nwamba

Источник: https://scotch.io/

Редакция: Команда webformyself.