
在任何用户认证系统中,密码的安全存储和验证是至关重要的。直接存储明文密码是极不安全的行为,一旦数据库泄露,所有用户密码将面临风险。因此,业界普遍采用哈希算法对密码进行单向加密存储。当用户尝试登录时,系统会对其输入的密码进行相同的哈希处理,然后将结果与数据库中存储的哈希值进行比较。
bcrypt是Node.js环境中常用的密码哈希库,以其计算成本高、抗彩虹表攻击能力强而闻名。然而,由于bcrypt依赖于C++插件,在某些环境下可能会出现编译或兼容性问题,导致诸如“Cannot find module napi-v3/bcrypt_lib.node”之类的错误,进而影响密码的哈希和比较功能。为了解决这些潜在问题,我们推荐使用纯JavaScript实现的bcryptjs库,它提供了与bcrypt相同的功能和兼容性,但避免了原生模块的依赖。
bcryptjs是一个功能与bcrypt完全兼容的库,但它完全由JavaScript编写,避免了原生模块可能带来的兼容性问题。以下是集成bcryptjs到Node.js应用中的详细步骤。
首先,您需要将bcryptjs添加到您的项目依赖中:
npm install bcryptjs
在用户注册流程中,当接收到用户的明文密码后,应立即对其进行哈希处理,并将哈希后的密码存储到数据库中。
const bcrypt = require('bcryptjs'); // 替换或同时引入 bcryptjs
// ... 其他代码 ...
app.post('/signup', async (req, res) => {
try {
const { firstName, lastName, email, role, password } = req.body;
// 检查邮箱是否已存在
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ message: 'Email already exists' });
}
// 设置默认密码(如果提供密码为空)
let plainTextPassword = password;
if (!plainTextPassword) {
plainTextPassword = 'defaultPassword123';
}
// 使用 bcryptjs 生成盐值并哈希密码
// genSaltSync 和 hashSync 是同步版本,但推荐使用异步版本以避免阻塞事件循环
const salt = await bcrypt.genSalt(10); // 异步生成盐值,成本因子为10
const hashedPassword = await bcrypt.hash(plainTextPassword, salt); // 异步哈希密码
// 创建新用户对象
const newUser = new User({
firstName,
lastName,
email,
role,
password: hashedPassword, // 存储哈希后的密码
});
// 保存用户到数据库
await newUser.save();
// ... 生成JWT令牌及其他响应逻辑 ...
res.status(201).json(authResponse);
} catch (error) {
console.error('Signup error:', error);
res.status(500).json({ message: 'Internal server error' });
}
});注意事项:
在用户登录流程中,从数据库中检索存储的哈希密码,并将其与用户输入的明文密码进行比较。bcryptjs.compare() 方法会处理用户输入密码的哈希过程,然后与数据库中的哈希密码进行比较。
const bcrypt = require('bcryptjs'); // 替换或同时引入 bcryptjs
// ... 其他代码 ...
app.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
// 查找用户
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ message: 'Invalid email or password' });
}
// 获取数据库中存储的哈希密码
const hashedPasswordFromDb = user.password;
// 使用 bcryptjs 比较用户输入密码与存储的哈希密码
const passwordMatch = await bcrypt.compare(password, hashedPasswordFromDb);
if (!passwordMatch) {
return res.status(401).json({ message: 'Invalid email or password' });
}
// ... 生成JWT令牌及其他响应逻辑 ...
const token = jwt.sign({ email: user.email }, secretKey);
const expirationDate = new Date().getTime() + 3600000; // 1小时过期
const loggedInUser = {
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
role: user.role,
id: user._id,
_token: token,
_tokenExpirationDate: expirationDate,
};
const authResponse = new AuthResponseData(loggedInUser);
res.status(200).json(authResponse);
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ message: 'Internal server error' });
}
});注意事项:
以下是一个整合了bcryptjs的server.js文件示例,展示了如何替换原有的bcrypt调用:
const express = require('express');
const bcrypt = require('bcryptjs'); // 使用 bcryptjs
const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');
const cors = require('cors');
const secretKey = 'your_jwt_secret_key'; // 替换为您的JWT密钥
const app = express();
app.use(cors());
app.use(express.json());
// MongoDB connection URI
const uri = 'mongodb://localhost:27017/final-year-project';
// Connect to the MongoDB database
mongoose
.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => {
console.log('Connected to the database');
app.listen(3000, () => {
console.log('App connected on port 3000');
});
})
.catch((error) => {
console.error('Failed to connect to the database:', error);
});
// Define the user schema
const userSchema = new mongoose.Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true, unique: true },
role: { type: String, required: true },
password: { type: String, required: true },
}, { collection: 'users' });
// Define the user model
const User = mongoose.model('User', userSchema);
class AuthResponseData {
constructor(user) {
this.user = user;
}
}
// Signup endpoint
app.post('/signup', async (req, res) => {
try {
const { firstName, lastName, email, role, password } = req.body;
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ message: 'Email already exists' });
}
let plainTextPassword = password;
if (!plainTextPassword) {
plainTextPassword = 'defaultPassword123';
}
// 使用 bcryptjs 异步生成盐值和哈希密码
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(plainTextPassword, salt);
const newUser = new User({
firstName,
lastName,
email,
role,
password: hashedPassword,
});
await newUser.save();
const token = jwt.sign({ email: newUser.email }, secretKey, { expiresIn: '1h' }); // JWT设置过期时间
const expirationDate = new Date().getTime() + 3600000;
const user = {
firstName: newUser.firstName,
lastName: newUser.lastName,
email: newUser.email,
role: newUser.role,
id: newUser._id,
_token: token,
_tokenExpirationDate: expirationDate,
};
const authResponse = new AuthResponseData(user);
res.status(201).json(authResponse);
} catch (error) {
console.error('Signup error:', error);
res.status(500).json({ message: 'Internal server error' });
}
});
// Login endpoint
app.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ message: 'Invalid email or password' });
}
const hashedPasswordFromDb = user.password;
// 使用 bcryptjs 异步比较密码
const passwordMatch = await bcrypt.compare(password, hashedPasswordFromDb);
if (!passwordMatch) {
return res.status(401).json({ message: 'Invalid email or password' });
}
const token = jwt.sign({ email: user.email }, secretKey, { expiresIn: '1h' }); // JWT设置过期时间
const expirationDate = new Date().getTime() + 3600000;
const loggedInUser = {
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
role: user.role,
id: user._id,
_token: token,
_tokenExpirationDate: expirationDate,
};
const authResponse = new AuthResponseData(loggedInUser);
res.status(200).json(authResponse);
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ message: 'Internal server error' });
}
});通过使用bcryptjs,您可以避免bcrypt原生模块可能带来的兼容性问题,从而在Node.js应用中实现更稳定、更可靠的密码哈希和比较功能。始终记住,密码安全是用户认证系统的基石,选择合适的工具并遵循最佳实践是构建安全应用的关键。异步处理哈希和比较操作,并合理设置工作因子,可以在保证安全性的同时,兼顾应用的性能。
以上就是安全地比较存储的哈希密码与用户输入密码的指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号