在验证socket.io
连接时,很少有资源可以探索。我最近完成了一个编程项目,该项目涉及认证套接字连接。在项目期间,我尽力搜索解释如何为socket.io
连接创建身份验证系统的资源,但无济于事。它们要么对我的项目的用例不起作用,要么不清楚。通过一些努力,我能够为服务器构建安全的身份验证系统,以便在建立连接之前将对每个连接客户端进行身份验证。
在本文中,我将带您完成使用JSON Web令牌(JWT)构建安全的socket.io
身份验证系统的分步过程。这些知识也可以转移到其他身份验证库,例如passport.js
或其他编程语言的身份验证库,因为我将使用Nodejs。
让我们潜水!
概述
我将解释示例代码的每个细节,让每个人都遵循,无论他们熟悉的技术堆栈如何。
但是,我们将使用MongoDB User
Collection,http
服务器,Express app
和socket.io
服务器实例构建身份验证系统。此外,我们将提供示例客户端代码以演示其用例。
User
集合用于存储和检索用户的凭据。
http
服务器用于收听HTTP和套接字连接请求。
Express app
用于设置将处理注册和登录身份验证端点的功能处理程序。预计包含JSON Web令牌(JWT)的响应是成功的身份验证。
socket.io
服务器实例负责管理套接字连接事件。中间件功能用于验证客户端发送的JWT,以确保只有身份验证的用户才能制作Web套接字连接请求。
客户端代码用于向身份验证端点提出HTTP请求并存储JWT响应,然后将其用于将Web套接字连接请求到socket.io
Server实例。
身份验证系统
在本节中,我们构建身份验证系统。从数据库模型到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模式和模型交互的功能。
使用mongoose.Schema
构造函数创建userSchema
,指定用户对象的结构和数据类型。该模式包括username
,email
和password
的字段,每个类型String
。
然后使用userSchema
创建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()
函数,该功能使我们能够注册端点及其各自的处理程序功能。此外,我们将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' });
}
}
我们首先从req.body
对象破坏username
,email
和password
,其中包含客户端发送的请求主体。接下来,我们通过在数据库中搜索email
和username
的唯一性进行检查。如果找到了任何一个,则返回400
不良请求响应。否则,该函数将使用BCRYPT模块提供的bcrypt.hash()
功能进行哈希密码。哈希密码以及破坏的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
与数据库中的用户存储密码不匹配,则返回400
不良响应。但是,如果密码匹配,我们将使用jsonwebtoken
模块生成JWT令牌。通过将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;
我们的Express app
现在可以使用。
插座服务器设置
现在,让我们设置http
和socket.io
服务器实例以进行身份验证。
const http = require('http');
const ioServer = require('socket.io');
const app = require('./app');
我们首先导入http
和socket.io
模块,以及我们的express app
。
接下来,我们继续创建http
和socket.io
服务器的实例。
// Create server from express app
const server = http.createServer(app);
// Create the socket server instance
const io = ioServer(server);
我们使用http
模块提供的createServer()
功能来创建HTTP服务器实例。然后将Express 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
}
在上述代码中,当客户端连接到服务器时,调用了由socket.io
库提供的中间件函数io.use()
。在功能内部,我们首先从客户端在握手(连接)期间发送的socket.handshake.auth.token
属性检索JWT令牌。然后,它使用存储在环境变量中的秘密密钥来验证和解码令牌。
如果令牌有效,则中间件会根据从令牌提取的用户ID从数据库中检索用户信息。如果找到用户,则将用户对象连接到套接字以供将来参考。
如果在身份验证过程中发生任何错误,例如无效令牌或找不到的用户,则会丢弃错误,并且中间件使用错误参数调用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
Server实例。
让我们从注册用户开始。
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
库是导入的。
然后,我们通过向服务器的/auth/register
端点提出POST
请求来注册用户。服务器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);
}
};
我们正在通过向服务器的/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-client
库提供的io
函数建立了与socket.io
服务器的客户端连接。
使用套接字服务器URL调用io
函数,将对象作为参数,其中包括auth
属性。此属性指定将在握手过程中发送到服务器的身份验证令牌。从登录请求中提供了较早保存的token
变量的值。
建立连接后,设置了client.on('connect')
事件处理程序以聆听“连接”事件。当客户端成功连接到服务器时,执行回调函数,记录“连接!”到控制台。
可以在适当的事件处理程序中添加其他事件处理和与服务器的通信。
结论
由于可用的资源有限,为socket.io
连接建立安全的身份验证系统可能是一项具有挑战性的任务。但是,通过利用JSON Web令牌(JWT),可以创建一个强大的身份验证系统。在本文中,我们介绍了构建此类系统的分步过程,包括设置数据库模型,使用Express创建身份验证端点,使用socket.io
实现身份验证中间件,并使用axios
和socket.io-client
演示客户端连接图书馆。通过遵循提供的代码示例和说明,开发人员可以为socket.io
连接构建自己的安全身份验证系统,仅允许身份验证的用户建立连接并与服务器进行交互。