使用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
排查与解决方案
可能原因及对应修复:
密码含隐藏空格:前端输入可能带首尾空格,在登录接口中对密码做去空格处理:
const trimmedPassword = password.trim(); const isMatch = await bcrypt.compare(trimmedPassword, user.password);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);哈希过程验证:在创建用户接口添加日志,确认原始密码与哈希后密码是否正确,核对日志哈希值与数据库存储值是否一致:
console.log("原始密码:", password); console.log("哈希后密码:", hashedPassword);bcrypt版本一致性:确保项目中
bcryptjs版本稳定,避免版本差异导致哈希算法不兼容。
内容的提问来源于stack exchange,提问作者Vishesh Gupta
相关产品推荐
相关产品推荐

