首页 > web前端 > js教程 > 正文

安全地比较存储的哈希密码与用户输入密码的指南

霞舞
发布: 2025-09-25 19:58:15
原创
551人浏览过

安全地比较存储的哈希密码与用户输入密码的指南

本文详细介绍了在Node.js应用中如何安全有效地比较存储的哈希密码与用户输入的密码。针对bcrypt库可能遇到的兼容性问题,文章推荐使用纯JavaScript实现的bcryptjs库,并提供了详细的安装、注册(哈希)和登录(比较)的代码示例,旨在帮助开发者构建更稳定可靠的用户认证系统。

引言:密码安全存储与验证的重要性

在任何用户认证系统中,密码的安全存储和验证是至关重要的。直接存储明文密码是极不安全的行为,一旦数据库泄露,所有用户密码将面临风险。因此,业界普遍采用哈希算法对密码进行单向加密存储。当用户尝试登录时,系统会对其输入的密码进行相同的哈希处理,然后将结果与数据库中存储的哈希值进行比较。

bcrypt是Node.js环境中常用的密码哈希库,以其计算成本高、抗彩虹表攻击能力强而闻名。然而,由于bcrypt依赖于C++插件,在某些环境下可能会出现编译或兼容性问题,导致诸如“Cannot find module napi-v3/bcrypt_lib.node”之类的错误,进而影响密码的哈希和比较功能。为了解决这些潜在问题,我们推荐使用纯JavaScript实现的bcryptjs库,它提供了与bcrypt相同的功能和兼容性,但避免了原生模块的依赖。

使用 bcryptjs 进行密码哈希与比较

bcryptjs是一个功能与bcrypt完全兼容的库,但它完全由JavaScript编写,避免了原生模块可能带来的兼容性问题。以下是集成bcryptjs到Node.js应用中的详细步骤。

1. 安装 bcryptjs

首先,您需要将bcryptjs添加到您的项目依赖中:

npm install bcryptjs
登录后复制

2. 在注册(Signup)时哈希密码

在用户注册流程中,当接收到用户的明文密码后,应立即对其进行哈希处理,并将哈希后的密码存储到数据库中。

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' });
    }
});
登录后复制

注意事项:

通义灵码
通义灵码

阿里云出品的一款基于通义大模型的智能编码辅助工具,提供代码智能生成、研发智能问答能力

通义灵码 31
查看详情 通义灵码
  • bcrypt.genSalt(10) 中的 10 是盐值生成的工作因子(或成本因子),值越大,哈希计算越慢,安全性越高,但也会消耗更多CPU资源。通常建议在8到12之间选择。
  • bcrypt.genSalt 和 bcrypt.hash 都是异步操作,应使用 await 或回调函数处理。使用 await 结合 async/await 语法可以使代码更简洁易读。

3. 在登录(Login)时比较密码

在用户登录流程中,从数据库中检索存储的哈希密码,并将其与用户输入的明文密码进行比较。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' });
    }
});
登录后复制

注意事项:

  • bcrypt.compare(plainTextPassword, hashedPassword) 方法接收两个参数:用户输入的明文密码和从数据库中取出的哈希密码。它会自动对明文密码进行哈希处理,然后与哈希密码进行比较。
  • bcrypt.compare 同样是异步操作,必须使用 await 或回调函数来获取比较结果。

完整示例代码(集成 bcryptjs)

以下是一个整合了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中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号