You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Express+JWT登录时bcrypt密码匹配失败问题排查

Express + JWT 登录接口 bcrypt 密码匹配失败问题

问题描述

使用Express和JWT开发登录接口,创建用户时通过bcryptjs对密码进行哈希处理,但登录时密码对比始终失败,接口返回Invalid credentials。

后端实现代码

const express = require("express");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const { verifyAdmin } = require("../middleware/authMiddleware");

const router = express.Router();

router.post("/login", async (req, res) => {
  const { username, password } = req.body;

  try {
    const user = await User.findOne({ username });
    if (!user) {
      return res.status(400).json({ message: "Invalid credentials" });
    }

    // 调试日志:打印接收的密码和存储的哈希密码
    console.log("Received password:", password);
    console.log("Stored hashed password:", user.password);

    const isMatch = await bcrypt.compare(password, user.password);
    console.log("Password match result:", isMatch);

    if (!isMatch) {
      return res.status(400).json({ message: "Invalid credentials" });
    }

    const payload = {
      user: {
        id: user.id,
        userType: user.userType,
      },
    };

    jwt.sign(
      payload,
      process.env.JWT_SECRET,
      { expiresIn: "1h" },
      (err, token) => {
        if (err) throw err;
        res.json({ token, user });
      }
    );
  } catch (err) {
    console.error(err.message);
    res.status(500).send("Server error");
  }
});

// 创建用户接口
router.post("/create",  async (req, res) => {
  const { name, username, password, userType, process } = req.body;

  try {
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = new User({
      name,
      username,
      password: hashedPassword,
      userType,
      process: userType === "Process Department" ? process : undefined,
    });

    await newUser.save();
    res.status(201).json({ message: "User created successfully" });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

module.exports = router;

前端创建用户代码(Redux)

// src/redux/reducers/userSlice.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import api from "../../api/axiosConfig";

export const createUser = createAsyncThunk(
  "user/createUser",
  async (userData, thunkAPI) => {
    try {
      const response = await api.post("/auth/create", userData);
      return response.data;
    } catch (error) {
      return thunkAPI.rejectWithValue(error.response.data.message);
    }
  }
);

const userSlice = createSlice({
  name: "user",
  initialState: {
    isLoading: false,
    success: false,
    error: null,
  },
  reducers: {
    resetState: (state) => {
      state.isLoading = false;
      state.success = false;
      state.error = null;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(createUser.pending, (state) => {
        state.isLoading = true;
        state.success = false;
        state.error = null;
      })
      .addCase(createUser.fulfilled, (state) => {
        state.isLoading = false;
        state.success = true;
        state.error = null;
      })
      .addCase(createUser.rejected, (state, action) => {
        state.isLoading = false;
        state.success = false;
        state.error = action.payload;
      });
  },
});

export const { resetState } = userSlice.actions;

export default userSlice.reducer;

前端登录代码(Redux)

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import api from "../../api/axiosConfig";

export const login = createAsyncThunk(
  "auth/login",
  async ({ username, password }, thunkAPI) => {
    try {
      const response = await api.post("/auth/login", {
        username,
        password,
      });
      return response.data;
    } catch (error) {
      return thunkAPI.rejectWithValue(error.response.data.message);
    }
  }
);

export const logout = createAsyncThunk("auth/logout", async (_, thunkAPI) => {
  // 执行必要的清理操作
  return true;
});

const authSlice = createSlice({
  name: "auth",
  initialState: {
    isLoading: false,
    user: null,
    error: null,
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(login.pending, (state) => {
        state.isLoading = true;
        state.error = null;
      })
      .addCase(login.fulfilled, (state, action) => {
        state.isLoading = false;
        state.user = action.payload.user;
        state.error = null;
      })
      .addCase(login.rejected, (state, action) => {
        state.isLoading = false;
        state.user = null;
        state.error = action.payload;
      })
      .addCase(logout.fulfilled, (state) => {
        state.user = null;
        state.error = null;
      });
  },
});

export default authSlice.reducer;

控制台调试信息

Received password: vishesh

Stored hashed password: 
$2a$10$xxZgmq63qYyIRZ5DypwjbOF4fthtMm/m4qJE6sOytsCVxJzn2SRQy

Password match result: false

排查与解决方案

可能原因及对应修复:

  1. 密码含隐藏空格:前端输入可能带首尾空格,在登录接口中对密码做去空格处理:

    const trimmedPassword = password.trim();
    const isMatch = await bcrypt.compare(trimmedPassword, user.password);
    
  2. User模型字段问题:检查User模型的password字段是否为String类型,且无额外pre-save钩子自动哈希(避免二次哈希)。示例模型:

    const mongoose = require('mongoose');
    const UserSchema = new mongoose.Schema({
      name: String,
      username: String,
      password: String,
      userType: String,
      process: String
    });
    module.exports = mongoose.model('User', UserSchema);
    
  3. 哈希过程验证:在创建用户接口添加日志,确认原始密码与哈希后密码是否正确,核对日志哈希值与数据库存储值是否一致:

    console.log("原始密码:", password);
    console.log("哈希后密码:", hashedPassword);
    
  4. bcrypt版本一致性:确保项目中bcryptjs版本稳定,避免版本差异导致哈希算法不兼容。

内容的提问来源于stack exchange,提问作者Vishesh Gupta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 13:04:53