当前位置:首页 > 文章列表 > 文章 > 前端 > 哈希密码比对方法及用户输入验证技巧

哈希密码比对方法及用户输入验证技巧

2025-10-12 11:27:33 0浏览 收藏

本篇文章主要是结合我之前面试的各种经历和实战开发中遇到的问题解决经验整理的,希望这篇《安全比对哈希密码与用户输入方法》对你有很大帮助!欢迎收藏,分享给更多的需要的朋友学习~

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

本文详细介绍了在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' });
    }
});

注意事项:

  • 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应用中实现更稳定、更可靠的密码哈希和比较功能。始终记住,密码安全是用户认证系统的基石,选择合适的工具并遵循最佳实践是构建安全应用的关键。异步处理哈希和比较操作,并合理设置工作因子,可以在保证安全性的同时,兼顾应用的性能。

终于介绍完啦!小伙伴们,这篇关于《哈希密码比对方法及用户输入验证技巧》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布文章相关知识,快来关注吧!

Word突然关闭怎么恢复未保存文件Word突然关闭怎么恢复未保存文件
上一篇
Word突然关闭怎么恢复未保存文件
百度网盘在线登录官网地址
下一篇
百度网盘在线登录官网地址
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ChatExcel酷表:告别Excel难题,北大团队AI助手助您轻松处理数据
    ChatExcel酷表
    ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
    3648次使用
  • Any绘本:开源免费AI绘本创作工具深度解析
    Any绘本
    探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
    3912次使用
  • 可赞AI:AI驱动办公可视化智能工具,一键高效生成文档图表脑图
    可赞AI
    可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
    3855次使用
  • 星月写作:AI网文创作神器,助力爆款小说速成
    星月写作
    星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
    5023次使用
  • MagicLight.ai:叙事驱动AI动画视频创作平台 | 高效生成专业级故事动画
    MagicLight
    MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
    4228次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码