使用 JWT 在 MERN 堆栈中请求登录身份验证的 POST 请求可能有什么问题?

What can be the issue with the POST request for a signin authentication in MERN stack using JWT?

我正在使用 MERN 堆栈构建社交媒体应用程序。

我正在使用 POSTMAN 来测试 后端 API.

下面是依赖列表,即 package.json 文件。

{
  "name": "SocialMediaApp",
  "version": "1.0.0",
  "description": "A simple MERN-stack based social media app.",
  "main": "index.js",
  "scripts": {
    "development": "nodemon"
  },
  "author": "Prithvi",
  "license": "MIT",
  "keywords": [
    "react",
    "node",
    "express",
    "mongodb",
    "mern"
  ],
  "dependencies": {
    "@hot-loader/react-dom": "^17.0.1",
    "compression": "^1.7.4",
    "cookie-parser": "^1.4.5",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "express-jwt": "^6.0.0",
    "helmet": "^4.4.1",
    "jshint": "^2.12.0",
    "jsonwebtoken": "^8.5.1",
    "loadash": "^1.0.0",
    "mongodb": "^3.6.4",
    "mongoose": "^5.12.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-hot-loader": "^4.13.0"
  },
  "devDependencies": {
    "@babel/core": "^7.13.10",
    "@babel/preset-env": "^7.13.10",
    "@babel/preset-react": "^7.12.13",
    "babel-loader": "^8.2.2",
    "file-loader": "^6.2.0",
    "nodemon": "^2.0.7",
    "webpack": "^5.24.4",
    "webpack-cli": "^4.5.0",
    "webpack-dev-middleware": "^4.1.0",
    "webpack-hot-middleware": "^2.25.0",
    "webpack-node-externals": "^2.5.2"
  }
}

实际上,white 试图测试 后端 APIPOST 端点登录请求http://localhost:3000/auth/signin,我收到如下所示的错误,错误代码为 401 Unauthorized

      
{
  "error": "Could not sign in ! :("
}
    

下面是用户模型文件,即user.model.js.

import mongoose from "mongoose";
import crypto from "crypto";

const UserSchema = new mongoose.Schema({
  name: {
    type: String,
    trim: true,
    required: 'Name is required',
  },
  email: {
    type: String,
    trim: true,
    unique: 'Email already exists',
    match: [/.+\@.+\..+/, 'Please fill a valid email address'],
    required: 'Email is required !',
  },
  hashed_password: {
    type: String,
    required: 'Password is required !',
  },
  salt: String,
  updated: Date,
  created: {
    type: Date,
    default: Date.now,
  },
});

/**
 * The password string that's provided by the user is not stored directly in the user
 * document. Instead, it is handled as a virtual field.
 */
UserSchema
  .virtual('password')
  .set(function (password) {
    this._password = password;
    this.salt = this.makeSalt();
    this.hashed_password = this.encryptPassword(password);
  })
  .get(function () {
    return this._password;
  });

/**
 * To add validation constraints to the actual password string that's selected by the end
 * user, we need to add custom validation logic and associate it with the hashed_password
 * field in the schema.
 */
UserSchema.path('hashed_password').validate(function (v) {
  if (this._password && this._password.length < 6) {
    this.invalidate('password', 'Password must be atleast 6 characters.');
  }
  if (this.isNew && !this._password) {
    this.invalidate('password', 'Password is required.');
  }
}, null);

/**
 * The encryption logic and salt generation logic, which are used to generate the
 * hashed_password and salt values representing the password value, are defined as
 * UserSchema methods.
 */
UserSchema.methods = {
  authenticate: function (plainText) {
    return this.authenticate(plainText) === this.hashed_password;
  },
  encryptPassword: function (password) {
    if (!password) return '';
    try {
      return crypto
        .createHmac('sha1', this.salt)
        .update(password)
        .digest('hex');
    } catch (err) {
      return '';
    }
  },
  makeSalt: function () {
    return Math.round(new Date().valueOf() * Math.random()) + '';
  },
};

export default mongoose.model('User', UserSchema);

下面是控制器文件auth.controller.js.

import User from "../models/user.model";
import jwt from 'jsonwebtoken';
import expressJwt from "express-jwt";
import config from "./../../config/config";

const signin = async (req, res) => {
    try {
        let user = await User.findOne({
            "email": req.body.email
        });
        if (!user) {
            return res.status('401').json({
                error: "User not found"
            });
        }

        if (!user.authenticate(req.body.password)) {
            return res.status('401').send({
                error: "Email and passwords don't match."
            });
        }

        const token = jwt.sign({
            _id: user._id,
        }, config.jwtSecret);

        res.cookie('t', token, {
            expire: new Date() + 9999
        });

        return res.json({
            token,
            user: {
                _id: user._id,
                name: user.name,
                email: user.email
            }
        });
    } catch (err) {
        return res.status('401').json({
            error: "Could not sign in ! :("
        });
    }
};

const signout = (req, res) => {
    res.clearCookie("t");
    return res.status('200').json({
        message: "signed out"
    });
};

const requireSignin = expressJwt({
    secret: config.jwtSecret,
    userProperty: 'auth',
    algorithms: ['HS256']
});

const hasAuthorization = (req, res, next) => {
    const authorized = req.profile && req.auth && req.profile._id == req.auth._id;
    if (!(authorized)) {
        return res.status('403').json({
            error: "User isn't authorized !"
        });
    }
    next();
};

export default {
    signin,
    signout,
    requireSignin,
    hasAuthorization
}

这里是路由文件auth.routes.js.

import express from "express";
import authCtrl from "../controllers/auth.controller";

const router = express.Router();

router.route('/auth/signin')
    .post(authCtrl.signin);
router.route('/auth/signout')
    .get(authCtrl.signout);

export default router;

最后,express.js 文件。

import express from "express";
import path from "path";
import cookieParser from "cookie-parser";
import compress from "compression";
import cors from "helmet";
import helmet from "cors";
import Template from "./../template";
import userRoutes from "./routes/user.routes";
import authRoutes from "./routes/auth.routes";

const CURRENT_WORKING_DIR = process.cwd();
const app = express();

app.use(express.json());
app.use(express.urlencoded({
  extended: true
}));
app.use(cookieParser());
app.use(compress());
app.use(helmet());
app.use(cors());

app.use('/dist', express.static(path.join(CURRENT_WORKING_DIR, 'dist')));

app.use('/', userRoutes);
app.use('/', authRoutes);

app
  .get("/", (req, res) => {
    res.status(200).send(Template());
  });

app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    res.status(401).json({
      "error": err.name + ": " + err.message
    });
  } else if (err) {
    res.status(400).json({
      "error": err.name + ": " + err.message
    });
    console.log(err);
  }
});

export default app;

P.S

我在 user.model.js.

的终端中收到以下错误

RangeError: Maximum call stack size exceeded
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)
    at model.authenticate (webpack://SocialMediaApp/./server/models/user.model.js?:80:17)

const token = jwt.sign({ _id: user.id, }, config.jwtSecret);

在这里你应该使用_id: user._id

我不确定为什么,但我对 import cors from 'helmet'import helmet from 'cors'

很感兴趣

编辑: 您是否尝试 returning true in:

authenticate: function (plainText) { return this.authenticate(plainText) === this.hashed_password; }

如果没有错误了,递归循环就是你return,从authenticate.

我认为你正在尝试做这样的事情: 在方法 authenticate 中,只需再次调用 encryptPassword() 而不是 authenticate() 。您在没有控制的递归循环中调用 authenticate();这就是错误的原因。

UserSchema.methods = {
  authenticate: function (plainText) {
    return this.encryptPassword(plainText) === this.hashed_password;
  },
  encryptPassword: function (password) {
    if (!password) return '';
    try {
      return crypto
        .createHmac('sha1', this.salt)
        .update(password)
        .digest('hex');
    } catch (err) {
      return '';
    }
  },
  makeSalt: function () {
    return Math.round(new Date().valueOf() * Math.random()) + '';
  },
};