Vue یک فریموورک JavaScript پیشرونده است که ساخت برنامههای Frontend را آسانتر میکند. اگر آن را با Vue-Router ترکیب کنید، میتوانید برنامههایی با کارایی بالا، همراه routeهای دینامیک کامل بسازید. Vue-router یک ابزار کارآمد است که میتواند احراز هویت را برای برنامههای Vue، به صورت یکپارچه مدیریت کند. در این آموزش، به استفاده از Vue-router برای مدیریت احراز هویت و دسترسی کنترل برای بخشهای مختلف برنامه، نگاهی خواهیم داشت.
جدول محتویات:
- شروع کار
- راهاندازی سرور Node.js
- بروزرسانی فایل Vue-router
- تعریف برخی کامپوننتها
- راهاندازی Axios به صورت Global
- اجرای برنامه
- نتیجه گیری
شروع کار:
برای شروع، Vue CLI را نصب کنید و با استفاده از آن، یک برنامه Vue جدید بسازید:
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 راهاندازی خواهیم کرد، که احراز هویت را برای ما مدیریت میکند. ما از دیتابیس SQLite برای سرور Node.js خود استفاده خواهیم کرد. دستور زیر را برای نصب درایور SQLite اجرا کنید:
npm install --save sqlite3
از آنجایی که با رمزعبورها سر و کار خواهیم داشت، به راهی برای hash کردن رمز عبور نیاز خواهیم داشت، که برای این کار از bcrypt استفاده خواهیم کرد. دستور زیر را برای نصب آن اجرا کنید:
$ npm install --save bcrypt
همچنین ما راهی میخواهیم بتوانیم وقتی که یک کاربر درخواستی به بخشهای محافظت شده برنامه ارسال میکند، آن کاربر احراز هویت شده را تایید کنیم. برای این هدف، از JWT استفاده خواهیم کرد. دستور زیر را برای نصب پکیج JWT اجرا کنید:
$ npm install jsonwebtoken --save
برای خواندن دادههای json که به سرور ارسال میکنیم، به body-parser نیاز خواهیم داشت. دستور زیر را برای نصب آن اجرا کنید:
$ npm install --save body-parser
حال که همه چیز راهاندازی شده است، بیایید سرور Node.js خود را بسازیم. شاخه جدیدی به نام server بسازید. اینجا، جایی است که میتوانیم هرچیزی که برای ساخت backend نود استفاده خواهیم کرد را ذخیره کنیم. در شاخه سرور، فایل جدیدی بسازید و آن را با نام app.js ذخیره کنید. کد زیر را در آن قرار دهید:
"use strict";
const express = require('express');
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());
ما تمام پکیجهایی که برای برنامه خود نیاز داریم را وارد کردهایم، دیتابیس را تعریف کردهایم، و یک سرور و یک router نیز ساختهایم.
حال بیایید CORS را تعریف کنیم، تا مطمئن باشیم که به هیچ گونه خطاهای میان منبعی برخورد نمیکنیم:
// 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)
بسیاری از افراد در اینجا از پکیج CORS استفاده میکنند، اما ما هیچگونه پیکربندی پیچیدهای نداریم؛ پس همین کافی است.
حال بیایید route مربوط به ثبت نام یک کاربر جدید را تعریف کنیم:
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 });
});
});
});
یک سری اتفاقات در اینجا میافتند. در ابتدا، ما بدنه درخواست را به متد دیتابیس (که بعدا تعریف خواهیم کرد) ارسال میکنیم، و یک تابع callback را منتقل میکنیم که پاسخ برگردانده شده از عملیات دیتابیس را مدیریت میکند. همانطور که انتظار میرود، ما یک سری بررسی خطا تعریف کردهایم، تا فراهم کردن اطلاعات دقیق به کاربر را تضمین کنیم.
وقتی که یک کاربر با موفقیت ثبت نام شده است، ما دادههای کاربر را بر حسب ایمیل انتخاب میکنیم و یک نشانه احراز هویت با استفاده از پکیج jwt که قبلا وارد کردیم، برای کاربر میسازیم. در فایل پیکربندی خود (که بعدا خواهیم ساخت) از یک کلید مخفی استفاده میکنیم تا ورودیها را تایید کنیم. به این صورت، میتوانیم یک نشانه ارسال شده به سرور را تایید کنیم.
حال، route مربوط به ثبت نام یک مدیر و ورود، که مشابه ثبت نام معمولی است را تعریف کنید:
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.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 });
});
})
ما از bcrypt برای مقایسه رمز عبور hash شده خود با رمز عبور وارد شده توسط کاربر برای ورود استفاده میکنیم. اگر این دو مورد یکی باشند، کاربر را وارد میکنیم. اگر هم نباشند، میتوانید هرگونه که میخواهید به کاربر پاسخ دهید.
حال بیایید از سرور خود استفاده کنیم و برنامه خود را قابل دسترسی کنیم:
app.use(router)
let port = process.env.PORT || 3000;
let server = app.listen(port, function() {
console.log('Express server listening on port ' + port)
});
ما سروری بر روی پورت 3000 یا هر پورت دینامیکی که توسط سیستم ما ساخته شده است، ساختیم.
حال فایل دیگری به نام 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,
is_admin integer)`
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، تمام routeهایی که میخواهیم برنامهمان داشته باشد را تعریف خواهیم کرد. این با کاری که با سرور خود کردیم متفاوت است و نباید شما را گیج کند.
فایل مربوطه را باز کنید و این کد را به آن اضافه کنید:
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)
ما تمام کامپوننتهایی که برنامهمان استفاده خواهد کرد را وارد کردهایم. حال در اینجا، کامپوننتها را خواهیم ساخت.
حال، بیایید routeها را برای برنامه خود تعریف کنیم:
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
}
},
]
})
Vue-router ما را قادر میسازد تا یک meta بر روی routeهای خود تعریف کنیم، که بتوانیم برخی رفتارهای اضافی را مشخص کنیم. در این مورد، برخی routeها را به عنوان مهمان، (که یعنی فقط کاربران مهمان میتوانند آن را ببیند) برخی را با نیاز به احراز هویت (که یعنی فقط کاربران احراز هویت شده میتوانند آن را ببینند) و آخرین مورد که فقط برای کاربران مدیر قابل دسترسی است را تعریف کردهایم.
حال، بیایید درخواستهای ارسال شده به این routeها را بر پایه مشخصات meta مدیریت کنیم:
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
Vue-router متدی به نام beforeEach دارد که قبل از پردازش هر router فراخوانی میشود. اینجا، جایی است که میتوانیم شرط بررسی خود را تعریف کنیم و دسترسی کاربر را محدود کنیم. این متد سه پارامتر را میگیرد: to، from و next. to جایی است که کاربر میخواهد برود، from جایی است که کاربر از آن آمده است و next نیز یک تابع callback است که پردازش درخواست کاربر را ادامه میدهد. بررسی ما، بر روی آبجکت to است.
ما برخی موارد را بررسی میکنیم:
- اگر route مربوطه نیاز به احراز هویت دارد، (requireAuth) به دنبال نشانه jwt که نشان میدهد کاربر وارد شده است، بگرد.
- اگر route مربوطه نیاز به احراز هویت دارد، (requireAuth) و فقط برای کاربران مدیر است، احراز هویت و وضعیت مدیر بودن کاربر را بررسی کن.
- اگر route مربوطه برای مهمان (guest) است، بررسی کن که کاربر وارد شده است یا نه.
بر حسب چیزی که بررسی میکنیم، کاربر را انتقال میدهیم. ما از نام routeها برای انتقال استفاده میکنیم؛ پس مطمئن شوید که از آن در برنامه خود استفاده میکنید.
تعریف برخی کامپوننتها
برای آزمایش چیزی که ساختیم، بیایید برخی کامپوننتهای جدید را تعریف کنیم. در شاخه ./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>
حال کارمان با الگوی HTML خود تمام شد. بیایید اسکریپت مربوط به مدیریت ورود را تعریف کنیم:
<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 => {
})
.catch(function (error) {
console.error(error.response);
});
}
}
}
}
</script>
در این نقطه، ما دو صفت با نامهای email و password داریم که به فیلدهای فرم متصل شدهاند تا ورودی کاربر را دریافت کنند. ما درخواستی به سرور خود ارسال کردیم، تا مواردی که کاربر وارد میکند را احراز هویت کنیم.
حال بیایید از پاسخ سرور استفاده کنیم:
[...]
methods : {
handleSubmit(e){
[...]
.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')
}
}
}
})
[...]
}
}
}
}
ما نشانه jwt و اطلاعات user را در localStorage ذخیره میکنیم تا بتوانیم از تمام بخشهای برنامه خود به آنها دسترسی داشته باشیم. البته، ما کاربر را قبل از این که به صفحه ورود منتقل کنیم، به هر بخش برنامهمان که میخواست دسترسی داشته باشد، منتقل میکنیم. اگر هم به شاخه ورود بیایند، آنها را بر حسب نوع کاربر منتقل میکنیم.
سپس، فایل جدیدی به نام Register.js بسازید و این کد را به آن اضافه کنید:
<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 به صورت Global
ما برای تمام درخواستهای سرور خود، از 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 را اضافه کردهایم. حال میتوانیم از Axios در تمام کامپوننتهای خود به صورت this.$http استفاده کنیم.
اجرای برنامه
حال که کار ما با برنامه به اتمام رسیده است، باید تمام تمام assetهای خود را بسازیم و آن را اجرا کنیم. از آنجایی که یک سرور Node.js به همراه برنامه Vue خود داریم، به هر دوی آنها برای کار کردن برنامه خود نیاز خواهیم داشت.
بیایید اسکریپتی اضافه کنیم که به ما در اجرای سرور Node خود کمک خواهد کرد. فایل 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 خود کمک کند. حال، دستور زیر را برای اجرای سرور اجرا کنید:
$ npm run server
باید چنین چیزی را ببینید:
سپس یک نمونه ترمینال دیگر بسازید و برنامه Vue را به این صورت اجرا کنید:
$ npm run dev
این کار، تمام assetها را میسازد و برنامه را اجرا میکند. میتوانید لینکهایی که نمایش داده میشود را باز کنید و برنامه را ببینید.
نتیجه گیری
در این آموزش، دیدیم که چگونه میتوانیم از Vue-router برای تعریف بررسیها بر روی routeها استفاده کنیم و از دسترسی کاربران به routeهایی خاص، جلوگیری کنیم. همچنین دیدیم که چگونه میتوانیم کاربرها را به بخشهای مختلف برنامه خود، بر حسب وضعیت احراز هویت منتقل کنیم.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید