سیستم احراز هویت Socket.IO با JWT

منابع کمی برای بررسی در مورد احراز هویت وجود دارد socket.io
اتصالات من اخیراً یک پروژه برنامه نویسی را به پایان رساندم که شامل احراز هویت اتصالات سوکت بود. در طول پروژه، تمام تلاشم را کردم تا منابعی را جستجو کنم که نحوه ایجاد یک سیستم احراز هویت را توضیح دهد socket.io
اتصالات، اما فایده ای نداشت. آنها یا برای مورد استفاده پروژه من مفید نبودند یا نامشخص بودند. با کمی تلاش، من توانستم یک سیستم احراز هویت امن برای سرور بسازم تا هر کلاینت متصل قبل از برقراری ارتباط احراز هویت شود.
در این مقاله، من شما را با فرآیند گام به گام ساختن یک امن آشنا می کنم socket.io
سیستم احراز هویت با توکن های وب JSON (JWT). این دانش همچنین می تواند به کتابخانه های احراز هویت دیگری مانند passport.js
یا کتابخانه های احراز هویت زبان های برنامه نویسی دیگر، زیرا من از NodeJs استفاده خواهم کرد.
بیایید شیرجه بزنیم!
بررسی اجمالی
من تمام جزئیات کدهای نمونه را توضیح خواهم داد تا همه بدون توجه به پشته های فنی که با آنها آشنا هستند، آنها را دنبال کنند.
با این حال، ما سیستم احراز هویت را با استفاده از MongoDB خواهیم ساخت User
مجموعه، یک http
سرور، یک اکسپرس app
، و socket.io
نمونه سرور علاوه بر این، ما یک نمونه کد سمت سرویس گیرنده را برای نشان دادن موارد استفاده آن ارائه خواهیم کرد.
را User
مجموعه برای ذخیره و بازیابی اطلاعات کاربری کاربران استفاده می شود.
را http
سرور برای گوش دادن به HTTP و درخواست های اتصال سوکت استفاده می شود.
اکسپرس app
برای راهاندازی کنترلکنندههای تابع که نقاط پایانی احراز هویت ثبت نام و ورود به سیستم را مدیریت میکنند، استفاده میشود. پاسخی حاوی یک رمز وب JSON (JWT) در احراز هویت موفقیت آمیز انتظار می رود.
را socket.io
نمونه سرور مسئول مدیریت رویدادهای اتصال سوکت است. یک تابع میانافزار برای اعتبارسنجی JWT ارسال شده توسط مشتری استفاده میشود و اطمینان حاصل میکند که فقط کاربران احراز هویت شده میتوانند درخواست اتصال سوکت وب را انجام دهند.
کد سمت سرویس گیرنده برای ایجاد درخواست های HTTP به نقاط پایانی احراز هویت استفاده می شود و پاسخ JWT را ذخیره می کند، که سپس برای ایجاد درخواست های اتصال سوکت وب به socket.io
نمونه سرور
سیستم احراز هویت
در این بخش سیستم احراز هویت را می سازیم. از مدل پایگاه داده تا socket.io
راه اندازی میان افزار احراز هویت نمونه سرور.
مدل پایگاه داده
بیایید با ایجاد آن شروع کنیم User
طرح واره مدل
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: String,
email: String,
password: String,
});
const User = mongoose.model('User', userSchema);
module.exports = User;
ما وارد می کنیم mongoose
ماژول، که قابلیت تعریف و تعامل با طرحواره ها و مدل های MongoDB را فراهم می کند.
آ userSchema
با استفاده از ایجاد می شود mongoose.Schema
سازنده، ساختار و انواع داده های شی کاربر را مشخص می کند. این طرح شامل فیلدهایی برای username
، email
، و password
، هر کدام از نوع String
.
را userSchema
سپس برای ایجاد a استفاده می شود User
مدل. این مدل امکان تعامل با مجموعه “کاربر” در پایگاه داده MongoDB متصل را فراهم می کند.
در نهایت، User
مدل صادر می شود تا برای استفاده در سیستم احراز هویت در دسترس باشد.
کنترل کننده های احراز هویت اکسپرس
در اینجا، ما توابع کنترل کننده را برای نقاط پایانی ثبت نام و ورود به سیستم با استفاده از Express ایجاد خواهیم کرد.
اول از همه، اجازه دهید ماژول های لازم را وارد کنیم.
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require("models/user.model");
در کد بالا، express
، bcrypt
، و jsonwebtoken
همه ماژول ها به متغیرهای مربوطه خود اختصاص داده می شوند. علاوه بر این، User
مدل پایگاه داده برای فعال کردن ذخیره سازی و تأیید اطلاعات کاربر وارد شده است.
سپس Express را ایجاد می کنیم app
.
const app = express();
app.use(express.json());
// Register API endpoint
app.post('/auth/register', registerUser);
// Login API endpoint
app.post('/auth/login', loginUser);
ما با فراخوانی شروع می کنیم express()
تابع ارائه شده توسط ماژول اکسپرس، که به ما امکان می دهد نقاط پایانی را به همراه توابع کنترل کننده مربوطه آنها ثبت کنیم. علاوه بر این، ما را متصل می کنیم express.json()
میان افزار برای تجزیه بارهای درخواستی JSON. دو POST
متعاقباً متدها به Express اضافه می شوند app
، آن را قادر می سازد تا به درخواست های مشتری در /auth/register
و /auth/login
نقاط پایانی این درخواست ها توسط registerUser
و loginUser
توابع کنترل کننده، به ترتیب.
بعد، ما به توسعه منطق برای ادامه میدهیم registerUser
عملکرد کنترل کننده
// User Registration Handler Function
async function registerUser(req, res) {
try {
const { username, email, password } = req.body;
// Check if the username or email already exists
const existingUser = await User.findOne().or([{ username }, { email }]);
if (existingUser) {
return res.status(400).json({ message: 'Username or email already exists' });
}
// Hash the password
const hashedPassword = await bcrypt.hash(password, 10);
// Create a new user
const newUser = new User({
username,
email,
password: hashedPassword,
});
// Save the user to the database
await newUser.save();
res.status(201).json({ message: 'Registration successful' });
} catch (error) {
console.error('Registration error', error);
res.status(500).json({ message: 'Registration error' });
}
}
ما با تخریب ساختار شروع می کنیم username
، email
، و password
از req.body
شی، که حاوی بدنه درخواست ارسال شده توسط مشتری است. در مرحله بعد، ما یک بررسی برای منحصر به فرد بودن آن انجام می دهیم email
و username
با جستجوی آنها در پایگاه داده اگر یکی از آنها پیدا شد، الف 400
پاسخ بد به درخواست برگشت داده می شود. در غیر این صورت، تابع با استفاده از رمز عبور را هش می کند bcrypt.hash()
عملکرد ارائه شده توسط ماژول bcrypt. رمز عبور هش شده به همراه گذرواژه تخریب شده username
و email
خواص، سپس برای ماندگاری در پایگاه داده ذخیره می شوند. در صورت عدم وجود خطا، الف 201
پاسخ ایجاد شده برگردانده می شود. با این حال، اگر در حین اجرای تابع کنترل کننده خطایی رخ دهد، الف 500
پاسخ خطای سرور داخلی برگردانده می شود.
بعد، ما به توسعه منطق برای ادامه می دهیم loginUser
عملکرد کنترل کننده
// ...
// User Login Handler Function
async function loginUser(req, res) {
try {
const { username, password } = req.body;
// Check if the username exists
const user = await User.findOne({ username });
if (!user) {
return res.status(400).json({ message: 'Invalid username or password' });
}
// Compare the password
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(400).json({ message: 'Invalid username or password' });
}
// Generate a JWT
const token = jwt.sign({ userId: user._id }, process.env.SECRET_KEY);
res.json({ token, message: 'Login successful' });
} catch (error) {
console.error('Login error', error);
res.status(500).json({ message: 'Login error' });
}
}
// ...
ما ابتدا بررسی می کنیم که آیا تخریب شده است یا خیر username
در پایگاه داده وجود دارد. اگر وجود نداشته باشد، الف 400
پاسخ بد به درخواست برگشت داده می شود. همچنین، اگر تخریب شده است password
با رمز عبور ذخیره شده توسط کاربر در پایگاه داده مطابقت ندارد، a 400
پاسخ بد برگردانده می شود. با این حال، اگر گذرواژهها مطابقت داشته باشند، با استفاده از کد JWT اقدام به تولید میکنیم jsonwebtoken
مدول. این نشانه با تنظیم ایجاد می شود userId
کلید ارزش _id
ویژگی کاربر، که به طور خودکار توسط MongoDB به هر اطلاعات کاربر ذخیره شده اختصاص داده می شود. با فرض عدم رخ دادن خطایی، یک پاسخ حاوی توکن تولید شده برگردانده می شود.
بعد، Express را صادر می کنیم app
.
// ...
module.exports = app;
کد کامل برای نقاط پایانی احراز هویت را می توانید در زیر بیابید.
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require("models/user.model");
const app = express();
app.use(express.json());
// Register API endpoint
app.post('/auth/register', registerUser);
// Login API endpoint
app.post('/auth/login', loginUser);
// User Registration Handler Function
async function registerUser(req, res) {
try {
const { username, email, password } = req.body;
// Check if the username or email already exists
const existingUser = await User.findOne().or([{ username }, { email }]);
if (existingUser) {
return res.status(400).json({ message: 'Username or email already exists' });
}
// Hash the password
const hashedPassword = await bcrypt.hash(password, 10);
// Create a new user
const newUser = new User({
username,
email,
password: hashedPassword,
});
// Save the user to the database
await newUser.save();
res.json({ message: 'Registration successful' });
} catch (error) {
console.error('Registration error', error);
res.status(500).json({ message: 'Registration error' });
}
}
// User Login Handler Function
async function loginUser(req, res) {
try {
const { username, password } = req.body;
// Check if the username exists
const user = await User.findOne({ username });
if (!user) {
return res.status(400).json({ message: 'Invalid username or password' });
}
// Compare the password
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(400).json({ message: 'Invalid username or password' });
}
// Generate a JWT
const token = jwt.sign({ userId: user._id }, process.env.SECRET_KEY);
res.json({ token, message: 'Login successful' });
} catch (error) {
console.error('Login error', error);
res.status(500).json({ message: 'Login error' });
}
}
module.exports = app;
اکسپرس ما app
اکنون آماده استفاده است.
راه اندازی سوکت سرور
حالا بیایید راه اندازی کنیم http
و socket.io
نمونه های سرور برای احراز هویت
const http = require('http');
const ioServer = require('socket.io');
const app = require('./app');
ما با واردات شروع می کنیم http
و socket.io
ماژول ها، و همچنین Express ما app
.
در مرحله بعد، به ایجاد نمونه هایی از the می پردازیم http
و socket.io
سرورها
// Create server from express app
const server = http.createServer(app);
// Create the socket server instance
const io = ioServer(server);
ما استفاده می کنیم createServer()
عملکرد ارائه شده توسط http
ماژول برای ایجاد یک نمونه سرور HTTP. اکسپرس app
سپس به تابع برای رسیدگی به درخواستها به نقاط پایانی احراز هویت منتقل میشود. را http
سرور سپس برای ایجاد یک استفاده می شود socket.io
نمونه سرور
بعد، ما به اضافه کردن میان افزار احراز هویت ادامه می دهیم.
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
// Verify and decode the JWT
const decoded = jwt.verify(token, process.env.SECRET_KEY);
// Get the user information from the database
const user = await User.findById(decoded.userId);
if (!user) {
throw new Error('User not found');
}
// Attach the user object to the socket
socket.user = user;
next();
} catch (error) {
console.error('Authentication error', error);
next(new Error('Authentication error'));
}
});
io.on('connection', (socket) => {
// Handle Events after authentication
}
در کد بالا، زمانی که یک کلاینت به سرور متصل می شود، تابع میان افزار است io.use()
ارائه شده توسط socket.io
کتابخانه، فراخوانی می شود. در داخل تابع، ابتدا توکن JWT را از تابع بازیابی می کنیم socket.handshake.auth.token
اموال ارسال شده توسط مشتری در هنگام دست دادن (اتصال). سپس با استفاده از یک کلید مخفی ذخیره شده در یک متغیر محیطی، رمز را تأیید و رمزگشایی می کند.
اگر توکن معتبر باشد، میان افزار اطلاعات کاربر را از پایگاه داده بر اساس شناسه کاربری استخراج شده از توکن بازیابی می کند. اگر کاربر پیدا شود، شی کاربر برای مراجعات بعدی به سوکت متصل می شود.
اگر در طی فرآیند احراز هویت خطایی رخ دهد، مانند رمز نامعتبر یا یافت نشدن کاربر، خطایی ایجاد میشود و میانافزار next()
تابع با آرگومان خطا
پس از احراز هویت موفق، io.on('connection')
کنترل کننده رویداد فعال می شود، که امکان مدیریت رویداد و ارتباط بیشتر با کاربر تأیید شده را فراهم می کند.
سیستم احراز هویت اکنون کامل است. اما برای گوش دادن به اتصالات، کد زیر را می توان به دلخواه اضافه یا سفارشی کرد.
server.listen(PORT, () => {
console.log(` Server started running at ${PORT}`);
});
جایی که PORT
پورت ترجیحی برای گوش دادن به اتصالات است. همچنین فراموش نکنید که قبل از راه اندازی سرور به مجموعه MongoDB خود متصل شوید.
کد کامل برای راه اندازی سرور را می توانید در زیر مشاهده کنید.
const http = require('http');
const ioServer = require('socket.io');
const app = require('./app');
// Create server from express app
const server = http.createServer(app);
// Create the socket server instance
const io = ioServer(server);
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
// Verify and decode the JWT
const decoded = jwt.verify(token, process.env.SECRET_KEY);
// Get the user information from the database
const user = await User.findById(decoded.userId);
if (!user) {
throw new Error('User not found');
}
// Attach the user object to the socket
socket.user = user;
next();
} catch (error) {
console.error('Authentication error', error);
next(new Error('Authentication error'));
}
});
io.on('connection', (socket) => {
// Handle Events after authentication
}
server.listen(PORT, () => {
console.log(` Server started running at ${PORT}`);
});
اتصال سمت مشتری
برای نشان دادن نحوه تنظیم سمت کلاینت برای استفاده از سیستم احراز هویت، از Axios برای ارسال درخواست به نقاط پایانی احراز هویت و استفاده از نشانه بازیابی شده برای اتصال به socket.io
نمونه سرور
بیایید با ثبت نام کاربر شروع کنیم.
const axios = require('axios');
const io = require('socket.io-client');
const username="HayatsCodes";
const email="hayatscodes@gmail.com";
const password = 123456;
let token;
try {
const response = await axios.post(`http://localhost:${PORT}/auth/register`, {
username,
email,
password,
});
console.log(response.data.message); // Registration successful
} catch (error) {
console.error(error);
}
اولا، axios
و socket.io-client
کتابخانه ها وارد می شوند.
سپس با ایجاد یک کاربر در حال ثبت نام هستیم POST
درخواست به /auth/register
نقطه پایانی سرور URL سرور با استفاده از PORT
متغیر، که باید شامل شماره پورت باشد.
مال کاربر username
، email
، و password
به عنوان محموله درخواستی ارائه می شود. ما استفاده می کنیم await
کلمه کلیدی برای ایجاد درخواست و ذخیره پاسخ در response
متغیر.
اگر ثبت نام موفقیت آمیز باشد، پیام پاسخ به کنسول وارد می شود.
اگر در فرآیند ثبت نام خطایی رخ دهد، catch
بلوک اجرا می شود و خطا با استفاده از آن در کنسول ثبت می شود console.error
.
حالا بیایید وارد کاربر ثبت نام شده شویم.
try {
const response = await axios.post(`http://localhost:${PORT}/auth/login`, {
username,
password,
});
token = response.data.token;
console.log(response.data.message); // login successful
} catch (error) {
console.error(error.response.data);
}
};
ما با ارسال یک درخواست POST به کاربر ثبت نام شده وارد می شویم /auth/login
نقطه پایانی سرور در حالی که شامل username
و password
در محموله درخواستی
در صورت موفقیت آمیز بودن ورود، توکن از آن استخراج می شود response.data.token
اموال، و message
به کنسول وارد شده است.
سپس توکن به آن اختصاص داده می شود token
متغیر برای استفاده در socket.io
اتصال مشتری
اگر در فرآیند ثبت نام خطایی رخ دهد، catch
بلوک اجرا می شود و خطا با استفاده از آن در کنسول ثبت می شود console.error
.
حالا بیایید به socket.io
سرور
const client = io(`http://localhost:${PORT}`, {
auth: {
token
}
});
// handle events
client.on('connect', () => { console.log('connected!') });
// Additional event handling can follow
در قطعه کد بالا، ما در حال ایجاد یک اتصال سمت کلاینت به آن هستیم socket.io
سرور با استفاده از io
عملکرد ارائه شده توسط socket.io-client
کتابخانه
را io
تابع با URL سرور سوکت و یک شی به عنوان آرگومان فراخوانی می شود که شامل عبارت است auth
ویژگی. این ویژگی نشانه احراز هویت را مشخص می کند که در طی فرآیند دست دادن به سرور ارسال می شود. مقدار ذخیره شده قبلی token
متغیر از درخواست ورود به سیستم به عنوان مقدار توکن ارائه می شود.
پس از برقراری ارتباط، client.on('connect')
کنترل کننده رویداد برای گوش دادن به رویداد “connect” تنظیم شده است. هنگامی که کلاینت با موفقیت به سرور متصل می شود، عملکرد پاسخ به تماس اجرا می شود و “متصل است!” به کنسول
مدیریت رویداد و ارتباط اضافی با سرور را می توان در کنترل کننده رویداد مناسب اضافه کرد.
نتیجه
ساخت یک سیستم احراز هویت امن برای socket.io
اتصالات به دلیل منابع محدود موجود می تواند یک کار چالش برانگیز باشد. با این حال، با استفاده از نشانه های وب JSON (JWT)، می توان یک سیستم احراز هویت قوی ایجاد کرد. در این مقاله به مراحل گام به گام ساخت چنین سیستمی از جمله راه اندازی مدل پایگاه داده، ایجاد نقاط پایانی احراز هویت با Express، پیاده سازی میان افزار احراز هویت با socket.io
و نشان دادن اتصالات سمت سرویس گیرنده با استفاده از axios
و socket.io-client
کتابخانه با دنبال کردن مثال ها و توضیحات کد ارائه شده، توسعه دهندگان می توانند سیستم احراز هویت امن خود را برای خود بسازند socket.io
اتصالات، تنها به کاربران تایید شده اجازه می دهد تا ارتباط برقرار کنند و با سرور تعامل داشته باشند.