使用 PassportJS 构建 NodeJS Web 应用进行身份验证

2025-06-07

使用 PassportJS 构建 NodeJS Web 应用进行身份验证

更新啦!!!
本教程使用PassportJS通过 MySQL 数据库管理软件 (DBMS) 对 NodeJS 应用进行身份验证。我写这篇文章的原因很简单。在我学习 Web 开发的过程中,我遇到了一个挑战,那就是在开发我的第二个项目时,如何将 PassportJS 身份验证集成到我的应用中。当时我使用MySQL进行数据库管理,使用SequelizeJS(一个用于基于 SQL 数据库的对象关系映射器 (ORM)),在本例中是 MySQL,并使用ExpressJS中间件、Body ParserExpress Session进行服务器和会话管理。困难在于,我只能找到使用Handlebars作为 ORM 并使用MongoDB作为 DBMS 的教程,而我当时对这些 DBMS 还不太熟悉,所以如果你正处于这个十字路口,那么这篇文章就是为你准备的。我不会浪费你的时间,而是会立即开始讲解。我会尽可能地直白,以便即使是 Web 开发知识最浅显的人也能理解。我们需要为这个设置做一些准备工作。我使用的是 Windows 电脑,所以如果我所说的任何内容不适用于您的操作系统,请找到解决方法,尤其是我的建议,但我相信过程是相同的。

首先,您需要一台装有您常用的文本编辑器(我使用了 VS Code)、Web 浏览器(推荐 Google Chrome)、您常用的终端(推荐 Git Bash)以及您选择的 SQL DBMS 的电脑。我使用的是 MySQL Workbench 6.3 CE。为了方便您理解,我将以列表的形式逐一介绍这些步骤。当您准备好以上所有文件并正确设置后,请按照以下说明操作。我假设您已经创建了数据库,如果没有,我将指导您完成。

  1. 在计算机上任意位置创建一个文件夹。我更喜欢使用终端导航到我首选的位置,然后输入。在本教程中,我将在桌面上创建名为learningPassportJS 的mkdir nameOfProject文件夹。接下来,输入 cd nameOfProject 即可导航到项目文件夹。

  1. 在终端中,我们必须初始化文件夹来处理我们所有的 NodeJS 框架。您可以决定稍后再执行此操作,但如果您对此过程不熟悉,我建议您先执行此操作。通过输入并按 Enter 来执行此操作。这将使用package.jsonnpm init文件设置您的项目。此文件将包含所有预期的依赖项和许可证以及您的姓名等信息。为了达到我们的目的,我将一直按键盘上的 Enter 键来加载默认值,但我将把入口点设置为server.js。您可以随意将其更改为您喜欢的内容。请确保您的文本中使用小写字母,否则您必须自己输入。

  1. 初始化项目后,我们将touch server.js在终端上创建 server.js 文件。

  2. 现在让我们安装所有需要的依赖项。稍后我会解释为什么我们需要每个依赖项,但为了避免麻烦,我建议先全部安装。您可以稍后再安装它们,但您需要它们才能成功运行应用程序。npm i --save sequelize passport passport-local mysql2 mysql express express-session body-parser bcrypt-nodejs您可以像下面这样在一行中安装它们,也可以选择像下面这样单独安装它们。
    npm i --save sequelize
    npm i --save passport
    npm i --save passport-local
    npm i --save mysql2
    npm i --save mysql
    npm i --save express
    npm i --save express-session
    npm i --save body-parser
    npm i --save bcryptjs

添加操作--save可确保你的依赖项已添加并保存到 package.json 文件中。如果你想部署此应用,这一点至关重要。你将看到一个名为node_modules的新文件夹。请勿触碰它。这是 Node 在你的计算机上本地运行应用时所使用的。如果你在项目中使用 Git,请不要忘记node_modules将该.gitignore文件添加到项目根文件夹中的文件中。

感谢Jordan White的贡献,我认为值得一提的是,在使用 Sequelize 之前,您必须先安装 Sequelize CLI。您可以通过npm install -g sequelize-cli从您喜欢的终端运行来全局安装它,或者您也可以删除-g它来本地安装它。

  1. 在您常用的终端中打开创建的 server.js 文件,并在其中输入几行代码。不用担心,我会对所有代码进行大量的注释,以便您轻松理解我编写每一行代码的原因。您可以将下面的代码复制到您的服务器文件中。
// Requiring necessary npm middleware packages 
var express = require("express");
var bodyParser = require("body-parser");
var session = require("express-session");
// Setting up port
var PORT = process.env.PORT || 8080;
// Creating express app and configuring middleware 
//needed to read through our public folder
var app = express();
app.use(bodyParser.urlencoded({ extended: false })); //For body parser
app.use(bodyParser.json());
app.use(express.static("public"));
//
//we are doing a GET to test if our server is working fine
app.get('/', function(req, res) {    
       res.send('Welcome to Passport with Sequelize and without HandleBars');
});
//
//this will listen to and show all activities on our terminal to 
//let us know what is happening in our app
app.listen(PORT, function() {
    console.log("App listening on PORT " + PORT);
  });
Enter fullscreen mode Exit fullscreen mode

保存服务器文件。让我们运行服务器以确保它正常工作。在终端中输入npm start或即可。还记得运行 时的入口点吗?这就是运行 时调用的node server.jsnpm initnpm start

如果你到目前为止都按照说明操作,你应该会看到以下内容

打开浏览器并输入 localhost :8080。这将显示“欢迎使用带有 Sequelize 且不带 HandleBars 的 Passport”。干得好!你完成了这么多!你正在创建你的应用。如果你没有看到该页面,请从头开始查看步骤。你可以关闭服务器并返回到你的代码。

  1. 我从一开始就假设你可能已经创建了数据库。如果你还没有创建,或者不知道如何操作,也不用担心。只需打开你选择的 MySQL 程序,在查询命令行中输入CREATE DATABASE passport_demo;并运行它即可。你应该已经创建了一个名为“passport_demo”的数据库。

  2. 现在我们的服务器和数据库已经正常运行,是时候添加其他部分了。我们将配置并初始化我们的 Sequelize 模块。sequelize init:models & sequelize init:config在终端中输入以下命令并按 Enter 键即可完成此操作。
    运行此代码后,您应该会看到两个文件夹:modelsconfig
    打开 config 文件夹,您应该会看到一个config.json文件。打开它并编辑开发对象的设置,使其与您的设置匹配。如果您的数据库有密码,请在此处用引号输入。示例如下。

{
  "development": {
    "username": "root",
    "password": "yourpassword",
    "database": "passport_demo",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}
Enter fullscreen mode Exit fullscreen mode

返回并打开 models 文件夹。您应该会看到一个index.js文件。在我们的教程中,该文件应该保持不变,但如果您的 config 文件夹位于其他位置,您可以打开它并编辑第 37 列第 8 行的内容,使其路由到您的位置,因为它需要config.json文件才能正常工作。某些 Windows PC 还会报错,提示找不到 config 模块。请将文件中的反斜杠改为正斜杠,以修复该错误。

  1. models文件夹中创建一个名为user.js的新文件。这将使用 Sequelize 将我们的用户信息插入数据库。您可以根据需要创建多个模型文件。models 文件夹应包含您在数据库中创建的各种表插入。在本教程中,我们需要一个用户模型。我们将需要bcryptjs包来加密和解密用户创建或登录的密码。您的user.js文件应如下所示
// Requiring bcrypt for password hashing. Using the bcryptjs version as 
//the regular bcrypt module sometimes causes errors on Windows machines
var bcrypt = require("bcryptjs");
//
// Creating our User model
//Set it as export because we will need it required on the server
module.exports = function(sequelize, DataTypes) {
  var User = sequelize.define("User", {
    // The email cannot be null, and must be a proper email before creation
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true,
      validate: {
        isEmail: true
      }
    },
    // The password cannot be null
    password: {
      type: DataTypes.STRING,
      allowNull: false
    }
  });
  // Creating a custom method for our User model. 
  //This will check if an unhashed password entered by the 
  //user can be compared to the hashed password stored in our database
  User.prototype.validPassword = function(password) {
    return bcrypt.compareSync(password, this.password);
  };
  // Hooks are automatic methods that run during various phases of the User Model lifecycle
  // In this case, before a User is created, we will automatically hash their password

  User.hook("beforeCreate", function(user) {
    user.password = bcrypt.hashSync(user.password, bcrypt.genSaltSync(10), null);
  });
  return User;
};

//This is a fix by Samaila Philemon Bala in case you want to use ES6
//and the above is not working

//User.beforeCreate(user => {
  //  user.password = bcrypt.hashSync(
    //  user.password,
      //bcrypt.genSaltSync(10),
      //null
    //);
  //});
Enter fullscreen mode Exit fullscreen mode
  1. 让我们回到server.js文件并添加几行代码。我们需要让服务器读取 models 文件夹,并且同步插入和读取到数据库。你的 server.js 应该如下所示:
// Requiring necessary npm middleware packages 
var express = require("express");
var bodyParser = require("body-parser");
var session = require("express-session");
// Setting up port
var PORT = process.env.PORT || 8080;
//Import the models folder
var db = require("./models");
//
// Creating express app and configuring middleware 
//needed to read through our public folder
var app = express();
app.use(bodyParser.urlencoded({ extended: false })); //For body parser
app.use(bodyParser.json());
app.use(express.static("public"));
//
//we are doing a GET to test if our server is working fine
app.get('/', function(req, res) {    
       res.send('Welcome to Passport with Sequelize and without HandleBars');
});
//
//this will listen to and show all activities on our terminal to 
//let us know what is happening in our app
// Syncing our database and logging a message to the user upon success
db.sequelize.sync().then(function() {
  app.listen(PORT, function() {
    console.log("==> 🌎  Listening on port %s. Visit http://localhost:%s/ in your browser.", PORT, PORT);
  });
});
Enter fullscreen mode Exit fullscreen mode
  1. 现在让我们导航到 config 文件夹并创建另一个名为middleware的文件夹,然后在该文件夹中创建一个名为isAuthenticated.js的文件。你应该有/config/middleware/isAuthenticated.js。打开并编辑isAuthenticated.js文件以匹配此
// This is middleware for restricting routes a user is not allowed to visit if not logged in
module.exports = function(req, res, next) {
  // If the user is logged in, continue with the request to the restricted route
  if (req.user) {
    return next();
  }
  // If the user isn't' logged in, redirect them to the login page
  return res.redirect("/");
};
Enter fullscreen mode Exit fullscreen mode

这也将被导出,我们需要它来限制仅限登录用户访问的页面。

  1. 现在该设置护照了。在config文件夹中创建一个名为 passport.js 的文件。打开该文件并在文件中输入以下内容。注释已作说明。
//we import passport packages required for authentication
var passport = require("passport");
var LocalStrategy = require("passport-local").Strategy;
//
//We will need the models folder to check passport agains
var db = require("../models");
//
// Telling passport we want to use a Local Strategy. In other words,
//we want login with a username/email and password
passport.use(new LocalStrategy(
  // Our user will sign in using an email, rather than a "username"
  {
    usernameField: "email"
  },
  function(email, password, done) {
    // When a user tries to sign in this code runs
    db.User.findOne({
      where: {
        email: email
      }
    }).then(function(dbUser) {
      // If there's no user with the given email
      if (!dbUser) {
        return done(null, false, {
          message: "Incorrect email."
        });
      }
      // If there is a user with the given email, but the password the user gives us is incorrect
      else if (!dbUser.validPassword(password)) {
        return done(null, false, {
          message: "Incorrect password."
        });
      }
      // If none of the above, return the user
      return done(null, dbUser);
    });
  }
));
//
// In order to help keep authentication state across HTTP requests,
// Sequelize needs to serialize and deserialize the user
// Just consider this part boilerplate needed to make it all work
passport.serializeUser(function(user, cb) {
  cb(null, user);
});
//
passport.deserializeUser(function(obj, cb) {
  cb(null, obj);
});
//
// Exporting our configured passport
module.exports = passport;
Enter fullscreen mode Exit fullscreen mode
  1. 为了使我们的应用能够正常运行,我们需要能够向数据库进行 GET 和 POST 操作。例如,app.get我们在server.js文件中放置的代码块就是一个示例。让我们编写一段简洁的代码。在根文件夹中创建一个名为routes的文件夹,并创建两个名为api-routes.jshtml-routes.js的文件。api -routes.js将用于路由GETPOST往返数据库。打开api-routes.js并粘贴以下内容。注释说明了一切。
// Requiring our models and passport as we've configured it
var db = require("../models");
var passport = require("../config/passport");
//
module.exports = function(app) {
  // Using the passport.authenticate middleware with our local strategy.
  // If the user has valid login credentials, send them to the members page.
  // Otherwise the user will be sent an error
  app.post("/api/login", passport.authenticate("local"), function(req, res) {
    // Since we're doing a POST with javascript, we can't actually redirect that post into a GET request
    // So we're sending the user back the route to the members page because the redirect will happen on the front end
    // They won't get this or even be able to access this page if they aren't authed
    res.json("/members");
  });
//
  // Route for signing up a user. The user's password is automatically hashed and stored securely thanks to
  // how we configured our Sequelize User Model. If the user is created successfully, proceed to log the user in,
  // otherwise send back an error
  app.post("/api/signup", function(req, res) {
    console.log(req.body);
    db.User.create({
      email: req.body.email,
      password: req.body.password
    }).then(function() {
      res.redirect(307, "/api/login");
    }).catch(function(err) {
      console.log(err);
      res.json(err);
      // res.status(422).json(err.errors[0].message);
    });
  });
//
  // Route for logging user out
  app.get("/logout", function(req, res) {
    req.logout();
    res.redirect("/");
  });
//
  // Route for getting some data about our user to be used client side
  app.get("/api/user_data", function(req, res) {
    if (!req.user) {
      // The user is not logged in, send back an empty object
      res.json({});
    }
    else {
      // Otherwise send back the user's email and id
      // Sending back a password, even a hashed password, isn't a good idea
      res.json({
        email: req.user.email,
        id: req.user.id
      });
    }
  });
};
Enter fullscreen mode Exit fullscreen mode

先把html-routes.js放一边,之后再处理。我们需要它来处理登录和页面服务。

  1. 在server.js文件中,我们需要导入并初始化 Passport。请确保 Express 在 Passport 之前初始化,因为 Passport 需要 Express。服务器中的标记非常重要。app.get由于我们不需要它,我还会删除代码块。您的服务器文件应如下所示
// Requiring necessary npm packages
var express = require("express");
var bodyParser = require("body-parser");
var session = require("express-session");
// Requiring passport as we've configured it
var passport = require("./config/passport");
//
// Setting up port and requiring models for syncing
var PORT = process.env.PORT || 8080;
var db = require("./models");
//
// Creating express app and configuring middleware needed for authentication
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(express.static("public"));
// We need to use sessions to keep track of our user's login status
app.use(session({ secret: "keyboard cat", resave: true, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
//
// Requiring our routes
require("./routes/html-routes.js")(app);
require("./routes/api-routes.js")(app);
//
// Syncing our database and logging a message to the user upon success
db.sequelize.sync().then(function() {
  app.listen(PORT, function() {
    console.log("==> 🌎  Listening on port %s. Visit http://localhost:%s/ in your browser.", PORT, PORT);
  });
});
Enter fullscreen mode Exit fullscreen mode

请注意,我们还需要 html-routes。下一步是创建用户界面 (UI),以便能够捕获用户登录信息。这将与我们通常创建的常规 html 文件及其 css 和 js 文件相同,但这次我们将把它放在一个公共文件夹中。Express 将使用并解析该文件夹。如果您熟悉POSTMAN,现在可以使用它来测试服务器。

  1. 我创建了一个示例公共文件夹,其中包含所有文件。本教程将使用它。从Mediafire下载并解压到根文件夹。

  2. 查看 public 文件夹中的 html 文件。你会发现我GET使用APIs 捕获了注册、登录和会员页面。这样我们就可以轻松地将其传递到服务器。

  3. 现在打开html-routes.js并粘贴以下代码

// Requiring path to so we can use relative routes to our HTML files
var path = require("path");
//
// Requiring our custom middleware for checking if a user is logged in
var isAuthenticated = require("../config/middleware/isAuthenticated");
//
module.exports = function(app) {
//
  app.get("/", function(req, res) {
    // If the user already has an account send them to the members page
    if (req.user) {
      res.redirect("/members");
    }
    res.sendFile(path.join(__dirname, "../public/signup.html"));
  });
//
  app.get("/login", function(req, res) {
    // If the user already has an account send them to the members page
    if (req.user) {
      res.redirect("/members");
    }
    res.sendFile(path.join(__dirname, "../public/login.html"));
  });
//
  // Here we've add our isAuthenticated middleware to this route.
  // If a user who is not logged in tries to access this route they will be 
  //redirected to the signup page
  app.get("/members", isAuthenticated, function(req, res) {
    res.sendFile(path.join(__dirname, "../public/members.html"));
  });
};
Enter fullscreen mode Exit fullscreen mode

保存所有文件,然后使用npm start或运行服务器node server.js。如果服务器出现故障,请检查终端上的错误并复习本教程中的所有内容。看一看*/public/js/members.js*,您将能够理解获取用户信息所需的内容。Sequelize 会自动id为用户创建一个,以便您可以使用它来处理数据库中其他表的关联。
例如,如果您要在分类网站上进行身份验证,您必须确保有一个模型,例如要插入数据库的帖子、执行帖子的 api 路由、在发布到数据库之前和之后到 GET 页面的 html 路由。始终确保传递您isAuthenticated不希望用户在未登录的情况下访问的任何 html 路由。isAuthenticated如果使用,将始终检查访问权限。

我希望我的字面解释能够帮助您在不使用 Handlebars 或 MongoDB 的情况下弄清楚您的身份验证。

理查德·德布拉

文章来源:https://dev.to/gm456742/building-a-nodejs-web-app-using-passportjs-for-authentication-3ge2
PREV
DOOM……使用单个 DIV 和 CSS 渲染!🤯🔫💥
NEXT
面向 Angular 开发者的 Svelte