از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
احراز هویت و مجوز با JWT ها در Express.js در این مقاله، ما در مورد روش عملکرد JSON Web Tokens، مزایای آنها، ساختار آنها و روش استفاده از آنها برای انجام احراز هویت و مجوز اولیه در Express صحبت خواهیم کرد. لازم نیست تجربه قبلی با JSON Web Tokens داشته باشید زیرا ما…
سرفصلهای مطلب
معرفی
در این مقاله، ما در مورد روش عملکرد JSON Web Tokens، مزایای آنها، ساختار آنها و روش استفاده از آنها برای انجام احراز هویت و مجوز اولیه در Express صحبت خواهیم کرد.
لازم نیست تجربه قبلی با JSON Web Tokens داشته باشید زیرا از ابتدا در مورد آن صحبت خواهیم کرد.
برای بخش پیاده سازی، اگر تجربه قبلی داشته باشید، ترجیح داده می شود بیان، JavaScript ES6 و REST Clients.
توکن های وب JSON چیست؟
JSON Web Tokens (JWT) به عنوان روشی برای برقراری ارتباط امن بین دو طرف معرفی شده است. با معرفی شد RFC 7519 مشخصات توسط نیروی ضربت مهندسی اینترنت (IETF).
حتی اگر میتوانیم از JWT با هر نوع روش ارتباطی استفاده کنیم، امروزه JWT برای مدیریت احراز هویت و مجوز از طریق HTTP بسیار محبوب است.
ابتدا باید چند ویژگی HTTP را بدانید.
HTTP یک پروتکل بدون حالت است، به این معنی که یک درخواست HTTP وضعیت را حفظ نمی کند. سرور از هیچ درخواست قبلی که توسط همان مشتری ارسال شده است اطلاعی ندارد.
درخواست های HTTP باید مستقل باشند. آنها باید اطلاعات مربوط به درخواست های قبلی را که کاربر انجام داده است در خود درخواست درج کنند.
چند راه برای انجام این کار وجود دارد، با این حال، محبوب ترین راه تنظیم a است شناسه جلسه، که اشاره ای به اطلاعات کاربر است.
سرور این شناسه جلسه را در حافظه یا پایگاه داده ذخیره می کند. مشتری هر درخواست را با شناسه این جلسه ارسال می کند. سپس سرور می تواند با استفاده از این مرجع اطلاعاتی در مورد مشتری دریافت کند.
در اینجا نمودار روش عملکرد احراز هویت مبتنی بر جلسه است:
معمولاً این شناسه جلسه به عنوان یک کوکی برای کاربر ارسال می شود. قبلاً در مقاله قبلی Handling Authentication در Express.js به تفصیل در مورد این موضوع صحبت کردیم.
از سوی دیگر، با JWT، زمانی که مشتری یک درخواست احراز هویت را به سرور ارسال می کند، یک توکن JSON را برای مشتری ارسال می کند که شامل تمام اطلاعات مربوط به کاربر به همراه پاسخ است.
مشتری این توکن را به همراه تمام درخواست های بعدی ارسال می کند. بنابراین سرور نیازی به ذخیره اطلاعات مربوط به جلسه نخواهد داشت. اما در این رویکرد مشکلی وجود دارد. هر کسی می تواند با یک توکن جعلی JSON درخواست جعلی ارسال کند و وانمود کند که کسی نیست.
به عنوان مثال، فرض کنید پس از احراز هویت، سرور یک شی JSON را با نام کاربری و زمان انقضا به مشتری برمیگرداند. بنابراین از آنجایی که شی JSON قابل خواندن است، هر کسی می تواند آن اطلاعات را ویرایش کرده و درخواست ارسال کند. مشکل اینجاست که هیچ راهی برای تایید چنین درخواستی وجود ندارد.
اینجاست که امضای توکن وارد میشود. بنابراین بهجای ارسال یک رمز ساده JSON، سرور یک توکن امضا شده ارسال میکند که میتواند تأیید کند که اطلاعات بدون تغییر هستند.
در ادامه این مقاله با جزئیات بیشتری به آن خواهیم پرداخت.
در اینجا نمودار روش عملکرد JWT آمده است:
ساختار یک JWT
بیایید در مورد ساختار یک JWT از طریق یک توکن نمونه صحبت کنیم:
همانطور که در تصویر می بینید، سه بخش از این JWT وجود دارد که هر کدام با یک نقطه از هم جدا شده اند.
نوار کناری: رمزگذاری Base64 یکی از راههای اطمینان از خراب نبودن دادهها است، زیرا دادهها را فشرده یا رمزگذاری نمیکند، بلکه آنها را به شکلی رمزگذاری میکند که اکثر سیستمها بتوانند آن را درک کنند. شما می توانید هر متن رمزگذاری شده Base64 را به سادگی با رمزگشایی آنها بخوانید.
اولین بخش JWT هدر است که یک رشته کدگذاری شده با Base64 است. اگر هدر را رمزگشایی کنید چیزی شبیه به این خواهد بود:
{
"alg": "HS256",
"typ": "JWT"
}
بخش هدر شامل الگوریتم هش است که برای تولید علامت و نوع توکن استفاده شده است.
بخش دوم payload است که شامل شی JSON است که برای کاربر ارسال شده است. از آنجایی که این فقط با Base64 رمزگذاری شده است، هر کسی می تواند به راحتی آن را رمزگشایی کند.
توصیه می شود هیچ گونه داده حساسی مانند رمز عبور یا اطلاعات قابل شناسایی شخصی را در JWT ها وارد نکنید.
معمولاً بدنه JWT چیزی شبیه به این خواهد بود، اگرچه لزوماً اعمال نمی شود:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
بیشتر اوقات، sub
ویژگی شامل شناسه کاربر، ویژگی است iat
، که مخفف آن است صادر شده در، مهر زمانی است که توکن صادر می شود.
همچنین ممکن است برخی از خواص رایج مانند eat
یا exp
، که زمان انقضای توکن است.
بخش آخر امضای توکن است. این با هش کردن رشته ایجاد می شود base64UrlEncode(header) + "." + base64UrlEncode(payload) + secret
با استفاده از الگوریتمی که در قسمت هدر ذکر شده است.
را secret
یک رشته تصادفی است که فقط سرور باید آن را بداند. هیچ هش را نمی توان به متن اصلی تبدیل کرد و حتی یک تغییر کوچک در رشته اصلی منجر به هش متفاوتی می شود. بنابراین secret
نمی توان مهندسی معکوس کرد.
هنگامی که این امضا به سرور ارسال می شود، می تواند تأیید کند که کلاینت هیچ جزئیاتی را در شی تغییر نداده است.
طبق استانداردها، کلاینت باید این توکن را از طریق درخواست HTTP در یک هدر به نام سرور ارسال کند. Authorization
با فرم Bearer (JWT_TOKEN)
. بنابراین ارزش Authorization
هدر چیزی شبیه به این خواهد بود:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o
اگر میخواهید درباره ساختار یک توکن JWT اطلاعات بیشتری کسب کنید، میتوانید مقاله عمیق ما، درک توکنهای وب JSON را بررسی کنید. همچنین می توانید بازدید کنید jwt.io و با دیباگر آنها بازی کنید:
مزیت استفاده از JWT نسبت به روش های سنتی
همانطور که قبلاً بحث کردیم، JWT برخلاف احراز هویت مبتنی بر جلسه میتواند تمام اطلاعات مربوط به خود کاربر را در خود داشته باشد.
این برای مقیاسبندی برنامههای وب، مانند برنامههای وب با میکروسرویسها، بسیار مفید است. امروزه، معماری یک برنامه وب مدرن چیزی شبیه به این به نظر می رسد:
همه این سرویسها میتوانند یک سرویس باشند که با توجه به میزان مصرف منابع (CPU یا Memory Usage) هر سرور یا برخی از سرویسهای مختلف مانند احراز هویت و غیره توسط Load Balanser هدایت میشوند.
اگر از روشهای سنتی مجوز استفاده کنیم، مانند کوکیها، باید یک پایگاه داده را به اشتراک بگذاریم، مانند ردیسبرای به اشتراک گذاشتن اطلاعات پیچیده بین سرورها یا سرویس های داخلی. اما اگر راز را در میان سرویسهای میکرو به اشتراک بگذاریم، فقط میتوانیم از JWT استفاده کنیم و پس از آن به هیچ منبع خارجی دیگری برای مجوز دادن به کاربران نیاز نیست.
استفاده از JWT با Express
در این آموزش، ما یک برنامه وب ساده مبتنی بر میکروسرویس برای مدیریت کتابها در یک کتابخانه با دو سرویس ایجاد میکنیم. یکی از سرویس ها مسئول احراز هویت کاربر و دیگری مسئول مدیریت کتاب ها خواهد بود.
دو نوع کاربر وجود خواهد داشت – مدیران و اعضا. مدیران میتوانند کتابهای جدید را مشاهده و اضافه کنند، در حالی که اعضا فقط میتوانند آنها را مشاهده کنند. در حالت ایدهآل، آنها همچنین میتوانند کتابها را ویرایش یا حذف کنند. اما برای ساده نگه داشتن این مقاله تا حد امکان، به جزئیات آنچنانی نخواهیم پرداخت.
برای شروع، در شما terminal یک پروژه خالی Node.js را با تنظیمات پیش فرض مقداردهی کنید:
$ npm init -y
سپس، بیایید چارچوب Express را نصب کنیم:
$ npm install --save express
سرویس احراز هویت
سپس، بیایید یک فایل به نام ایجاد کنیم auth.js
، که سرویس احراز هویت ما خواهد بود:
const express = require('express');
const app = express();
app.listen(3000, () => {
console.log('Authentication service started روی port 3000');
});
در حالت ایده آل، ما باید از یک پایگاه داده برای ذخیره اطلاعات کاربر استفاده کنیم. اما برای ساده نگه داشتن آن، بیایید یک آرایه از کاربران ایجاد کنیم که از آنها برای احراز هویت استفاده خواهیم کرد.
برای هر کاربر، نقش وجود خواهد داشت – admin
یا member
به شی کاربر خود متصل می شود. همچنین، به یاد داشته باشید که اگر در یک محیط تولید هستید، رمز عبور را هش کنید:
const users = (
{
username: 'john',
password: 'password123admin',
role: 'admin'
}, {
username: 'anna',
password: 'password123member',
role: 'member'
}
);
اکنون می توانیم یک کنترل کننده درخواست برای ورود کاربر ایجاد کنیم. بیایید نصب کنیم jsonwebtoken ماژول، که برای تولید و تأیید توکن های JWT استفاده می شود.
همچنین، بیایید نصب کنیم body-parser
میان افزار برای تجزیه بدنه JSON از درخواست HTTP:
$ npm i --save body-parser jsonwebtoken
حالا بیایید این ماژول ها را در برنامه Express پیکربندی کنیم:
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');
app.use(bodyParser.json());
اکنون می توانیم یک کنترل کننده درخواست برای رسیدگی به درخواست ورود کاربر ایجاد کنیم:
const accessTokenSecret = 'youraccesstokensecret';
این راز شما برای امضای توکن JWT است. شما هرگز نباید این راز را به اشتراک بگذارید، در غیر این صورت یک بازیگر بد می تواند از آن برای جعل توکن های JWT برای دسترسی غیرمجاز به سرویس شما استفاده کند. هرچه این توکن دسترسی پیچیده تر باشد، برنامه شما امن تر خواهد بود. بنابراین سعی کنید از یک رشته تصادفی پیچیده برای این توکن استفاده کنید:
app.post('/login', (req, res) => {
// Read username and password from request body
const { username, password } = req.body;
// Filter user from the users array by username and password
const user = users.find(u => { return u.username === username && u.password === password });
if (user) {
// Generate an access token
const accessToken = jwt.sign({ username: user.username, role: user.role }, accessTokenSecret);
res.json({
accessToken
});
} else {
res.send('Username or password incorrect');
}
});
در این کنترلر، کاربری را جستجو کرده ایم که با نام کاربری و رمز عبور موجود در بدنه درخواست مطابقت داشته باشد. سپس یک نشانه دسترسی با یک شی JSON با نام کاربری و نقش کاربر ایجاد کرده ایم.
سرویس احراز هویت ما آماده است. بیایید آن را با اجرا راه اندازی کنیم:
$ node auth.js
پس از راه اندازی سرویس احراز هویت، بیایید یک درخواست POST ارسال کنیم و ببینیم آیا کار می کند یا خیر.
من از rest-client استفاده خواهم کرد بیخوابی برای انجام این. به راحتی می توانید از هر سرویس گیرنده استراحتی که ترجیح می دهید یا چیزی مانند Postman برای این کار استفاده کنید.
بیایید یک درخواست پست به http://localhost:3000/login
نقطه پایانی با JSON زیر:
{
"username": "john",
"password": "password123admin"
}
شما باید نشانه دسترسی را به عنوان پاسخ دریافت کنید:
{
"accessToken": "eyJhbGciOiJIUz..."
}
خدمات کتاب
با انجام این کار، اجازه دهید a ایجاد کنیم books.js
فایل برای خدمات کتاب ما.
ما فایل را با وارد کردن کتابخانههای مورد نیاز و راهاندازی برنامه Express شروع میکنیم:
const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
const app = express();
app.use(bodyParser.json());
app.listen(4000, () => {
console.log('Books service started روی port 4000');
});
پس از پیکربندی، برای شبیه سازی یک پایگاه داده، فقط یک آرایه از کتاب ها ایجاد می کنیم:
const books = (
{
"author": "Chinua Achebe",
"country": "Nigeria",
"language": "English",
"pages": 209,
"title": "Things Fall Apart",
"year": 1958
},
{
"author": "Hans Christian Andersen",
"country": "Denmark",
"language": "Danish",
"pages": 784,
"title": "Fairy tales",
"year": 1836
},
{
"author": "Dante Alighieri",
"country": "Italy",
"language": "Italian",
"pages": 928,
"title": "The Divine Comedy",
"year": 1315
},
);
اکنون، ما می توانیم یک کنترل کننده درخواست بسیار ساده برای بازیابی همه کتاب ها از پایگاه داده ایجاد کنیم:
app.get('/books', (req, res) => {
res.json(books);
});
زیرا کتاب های ما باید فقط برای کاربران تایید شده قابل مشاهده باشد. ما باید یک میان افزار برای احراز هویت ایجاد کنیم.
قبل از آن، رمز دسترسی را برای امضای JWT ایجاد کنید، درست مانند قبل:
const accessTokenSecret = 'youraccesstokensecret';
این توکن باید همان توکن مورد استفاده در سرویس احراز هویت باشد. با توجه به اینکه راز بین آنها به اشتراک گذاشته شده است، ما می توانیم با استفاده از سرویس احراز هویت احراز هویت کنیم و سپس به کاربران در سرویس کتاب مجوز دهیم.
در این مرحله، اجازه دهید میان افزار Express ایجاد کنیم که احراز هویت را مدیریت می کند process:
const authenticateJWT = (req, res, next) => {
const authHeader = req.headers.authorization;
if (authHeader) {
const token = authHeader.split(' ')(1);
jwt.verify(token, accessTokenSecret, (err, user) => {
if (err) {
return res.sendStatus(403);
}
req.user = user;
next();
});
} else {
res.sendStatus(401);
}
};
در این میان افزار، مقدار هدر مجوز را می خوانیم. از آنجا که authorization
هدر یک مقدار در قالب دارد Bearer (JWT_TOKEN)
، مقدار را با فاصله تقسیم کرده و توکن را از هم جدا کرده ایم.
سپس توکن را با JWT تأیید کردیم. پس از تأیید، ما آن را متصل می کنیم user
به درخواست اعتراض کنید و ادامه دهید. در غیر این صورت یک خطا برای مشتری ارسال می کنیم.
ما می توانیم این میان افزار را در کنترل کننده درخواست GET خود پیکربندی کنیم، مانند این:
app.get('/books', authenticateJWT, (req, res) => {
res.json(books);
});
بیایید سرور را بوت کنیم و بررسی کنیم که آیا همه چیز درست کار می کند یا خیر:
$ node books.js
اکنون می توانیم درخواستی را برای http://localhost:4000/books
نقطه پایانی برای بازیابی همه کتاب ها از پایگاه داده.
اطمینان حاصل کنید که هدر “Authorization” را تغییر دهید تا حاوی مقدار “Bearer (JWT_TOKEN)” باشد، همانطور که در تصویر زیر نشان داده شده است:
در نهایت، ما می توانیم کنترل کننده درخواست خود را برای ایجاد یک کتاب ایجاد کنیم. زیرا فقط یک admin
می تواند یک کتاب جدید اضافه کند، در این کنترلر باید نقش کاربر را نیز بررسی کنیم.
میتوانیم از میانافزار احراز هویت که در بالا استفاده کردهایم نیز در این مورد استفاده کنیم:
app.post('/books', authenticateJWT, (req, res) => {
const { role } = req.user;
if (role !== 'admin') {
return res.sendStatus(403);
}
const book = req.body;
books.push(book);
res.send('Book added successfully');
});
از آنجایی که میان افزار احراز هویت کاربر را به درخواست متصل می کند، می توانیم آن را واکشی کنیم role
از req.user
شی و به سادگی بررسی کنید که آیا کاربر یک است admin
. اگر چنین است، کتاب اضافه می شود، در غیر این صورت، یک خطا پرتاب می شود.
بیایید این را با مشتری REST خود امتحان کنیم. به عنوان یک وارد شوید admin
کاربر (با استفاده از همان روش بالا) و سپس آن را کپی کنید accessToken
و آن را با Authorization
هدر همانطور که در مثال قبلی انجام دادیم.
سپس میتوانیم یک درخواست POST به آن ارسال کنیم http://localhost:4000/books
نقطه پایانی:
{
"author": "Jane Austen",
"country": "United Kingdom",
"language": "English",
"pages": 226,
"title": "Pride and Prejudice",
"year": 1813
}
نوسازی رمز
در این مرحله، برنامه ما هم احراز هویت و هم مجوز سرویس کتاب را کنترل میکند، اگرچه یک مورد وجود دارد عمده اشکال در طراحی – توکن JWT هرگز منقضی نمی شود.
اگر این توکن به سرقت رفته باشد، آنها برای همیشه به حساب دسترسی خواهند داشت و کاربر واقعی نمیتواند دسترسی را لغو کند.
برای حذف این امکان، اجازه دهید کنترل کننده درخواست ورود خود را به روز کنیم تا رمز پس از یک دوره خاص منقضی شود. ما می توانیم این کار را با عبور از expiresIn
دارایی به عنوان گزینه ای برای امضای JWT.
هنگامی که یک توکن منقضی می کنیم، باید استراتژی تولید یک توکن جدید را نیز در صورت انقضا داشته باشیم. برای انجام این کار، یک توکن JWT جداگانه به نام a ایجاد می کنیم نشانه رفرش، که می تواند برای تولید یک مورد جدید استفاده شود.
ابتدا یک رمز رمز بهروزرسانی و یک آرایه خالی برای ذخیره توکنهای تازه ایجاد کنید:
const refreshTokenSecret = 'yourrefreshtokensecrethere';
const refreshTokens = ();
هنگامی که یک کاربر وارد سیستم می شود، به جای تولید یک توکن، هم توکن های تازه سازی و هم احراز هویت را ایجاد کنید:
app.post('/login', (req, res) => {
// read username and password from request body
const { username, password } = req.body;
// filter user from the users array by username and password
const user = users.find(u => { return u.username === username && u.password === password });
if (user) {
// generate an access token
const accessToken = jwt.sign({ username: user.username, role: user.role }, accessTokenSecret, { expiresIn: '20m' });
const refreshToken = jwt.sign({ username: user.username, role: user.role }, refreshTokenSecret);
refreshTokens.push(refreshToken);
res.json({
accessToken,
refreshToken
});
} else {
res.send('Username or password incorrect');
}
});
و اکنون، اجازه دهید یک کنترل کننده درخواست ایجاد کنیم که بر اساس توکن های جدید تولید می کند روی نشانه های تازه سازی:
app.post('/token', (req, res) => {
const { token } = req.body;
if (!token) {
return res.sendStatus(401);
}
if (!refreshTokens.includes(token)) {
return res.sendStatus(403);
}
jwt.verify(token, refreshTokenSecret, (err, user) => {
if (err) {
return res.sendStatus(403);
}
const accessToken = jwt.sign({ username: user.username, role: user.role }, accessTokenSecret, { expiresIn: '20m' });
res.json({
accessToken
});
});
});
اما در این مورد نیز یک مشکل وجود دارد. اگر نشانه رفرش از کاربر به سرقت رفته باشد، شخصی می تواند از آن برای تولید هر تعداد توکن جدید که می خواهد استفاده کند.
برای جلوگیری از این، اجازه دهید یک ساده پیاده سازی کنیم logout
تابع:
app.post('/logout', (req, res) => {
const { token } = req.body;
refreshTokens = refreshTokens.filter(token => t !== token);
res.send("Logout successful");
});
هنگامی که کاربر درخواست خروج از سیستم را می دهد، ما نشانه رفرش را از آرایه خود حذف می کنیم. این اطمینان را ایجاد می کند که وقتی کاربر از سیستم خارج می شود، هیچ کس نمی تواند از نشانه refresh برای ایجاد یک نشانه احراز هویت جدید استفاده کند.
نتیجه
در این مقاله شما را با JWT و روش پیاده سازی JWT با Express آشنا کرده ایم. امیدوارم اکنون اطلاعات خوبی در مورد روش کار JWT و روش پیاده سازی آن در پروژه خود داشته باشید.
مثل همیشه کد منبع در دسترس است GitHub.
(برچسبها برای ترجمه)# جاوا اسکریپت
منتشر شده در 1403-01-22 12:19:04