使用 PassportJS 构建 NodeJS Web 应用进行身份验证
更新啦!!!
本教程使用PassportJS通过 MySQL 数据库管理软件 (DBMS) 对 NodeJS 应用进行身份验证。我写这篇文章的原因很简单。在我学习 Web 开发的过程中,我遇到了一个挑战,那就是在开发我的第二个项目时,如何将 PassportJS 身份验证集成到我的应用中。当时我使用MySQL进行数据库管理,使用SequelizeJS(一个用于基于 SQL 数据库的对象关系映射器 (ORM)),在本例中是 MySQL,并使用ExpressJS中间件、Body Parser和Express Session进行服务器和会话管理。困难在于,我只能找到使用Handlebars作为 ORM 并使用MongoDB作为 DBMS 的教程,而我当时对这些 DBMS 还不太熟悉,所以如果你正处于这个十字路口,那么这篇文章就是为你准备的。我不会浪费你的时间,而是会立即开始讲解。我会尽可能地直白,以便即使是 Web 开发知识最浅显的人也能理解。我们需要为这个设置做一些准备工作。我使用的是 Windows 电脑,所以如果我所说的任何内容不适用于您的操作系统,请找到解决方法,尤其是我的建议,但我相信过程是相同的。
首先,您需要一台装有您常用的文本编辑器(我使用了 VS Code)、Web 浏览器(推荐 Google Chrome)、您常用的终端(推荐 Git Bash)以及您选择的 SQL DBMS 的电脑。我使用的是 MySQL Workbench 6.3 CE。为了方便您理解,我将以列表的形式逐一介绍这些步骤。当您准备好以上所有文件并正确设置后,请按照以下说明操作。我假设您已经创建了数据库,如果没有,我将指导您完成。
- 在计算机上任意位置创建一个文件夹。我更喜欢使用终端导航到我首选的位置,然后输入。在本教程中,我将在桌面上创建名为learningPassportJS 的
mkdir nameOfProject
文件夹。接下来,输入 cd nameOfProject 即可导航到项目文件夹。
- 在终端中,我们必须初始化文件夹来处理我们所有的 NodeJS 框架。您可以决定稍后再执行此操作,但如果您对此过程不熟悉,我建议您先执行此操作。通过输入并按 Enter 来执行此操作。这将使用package.json
npm init
文件设置您的项目。此文件将包含所有预期的依赖项和许可证以及您的姓名等信息。为了达到我们的目的,我将一直按键盘上的 Enter 键来加载默认值,但我将把入口点设置为server.js。您可以随意将其更改为您喜欢的内容。请确保您的文本中使用小写字母,否则您必须自己输入。
-
初始化项目后,我们将
touch server.js
在终端上创建 server.js 文件。 -
现在让我们安装所有需要的依赖项。稍后我会解释为什么我们需要每个依赖项,但为了避免麻烦,我建议先全部安装。您可以稍后再安装它们,但您需要它们才能成功运行应用程序。
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
它来本地安装它。
- 在您常用的终端中打开创建的 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);
});
保存服务器文件。让我们运行服务器以确保它正常工作。在终端中输入npm start
或即可。还记得运行 时的入口点吗?这就是运行 时调用的。node server.js
npm init
npm start
如果你到目前为止都按照说明操作,你应该会看到以下内容
打开浏览器并输入 localhost :8080。这将显示“欢迎使用带有 Sequelize 且不带 HandleBars 的 Passport”。干得好!你完成了这么多!你正在创建你的应用。如果你没有看到该页面,请从头开始查看步骤。你可以关闭服务器并返回到你的代码。
-
我从一开始就假设你可能已经创建了数据库。如果你还没有创建,或者不知道如何操作,也不用担心。只需打开你选择的 MySQL 程序,在查询命令行中输入
CREATE DATABASE passport_demo;
并运行它即可。你应该已经创建了一个名为“passport_demo”的数据库。 -
现在我们的服务器和数据库已经正常运行,是时候添加其他部分了。我们将配置并初始化我们的 Sequelize 模块。
sequelize init:models & sequelize init:config
在终端中输入以下命令并按 Enter 键即可完成此操作。
运行此代码后,您应该会看到两个文件夹:models和config。
打开 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"
}
}
返回并打开 models 文件夹。您应该会看到一个index.js文件。在我们的教程中,该文件应该保持不变,但如果您的 config 文件夹位于其他位置,您可以打开它并编辑第 37 列第 8 行的内容,使其路由到您的位置,因为它需要config.json文件才能正常工作。某些 Windows PC 还会报错,提示找不到 config 模块。请将文件中的反斜杠改为正斜杠,以修复该错误。
- 在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
//);
//});
- 让我们回到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);
});
});
- 现在让我们导航到 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("/");
};
这也将被导出,我们需要它来限制仅限登录用户访问的页面。
- 现在该设置护照了。在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;
- 为了使我们的应用能够正常运行,我们需要能够向数据库进行 GET 和 POST 操作。例如,
app.get
我们在server.js文件中放置的代码块就是一个示例。让我们编写一段简洁的代码。在根文件夹中创建一个名为routes的文件夹,并创建两个名为api-routes.js和html-routes.js的文件。api -routes.js将用于路由GET
和POST
往返数据库。打开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
});
}
});
};
先把html-routes.js放一边,之后再处理。我们需要它来处理登录和页面服务。
- 在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);
});
});
请注意,我们还需要 html-routes。下一步是创建用户界面 (UI),以便能够捕获用户登录信息。这将与我们通常创建的常规 html 文件及其 css 和 js 文件相同,但这次我们将把它放在一个公共文件夹中。Express 将使用并解析该文件夹。如果您熟悉POSTMAN,现在可以使用它来测试服务器。
-
我创建了一个示例公共文件夹,其中包含所有文件。本教程将使用它。从Mediafire下载并解压到根文件夹。
-
查看 public 文件夹中的 html 文件。你会发现我
GET
使用API
s 捕获了注册、登录和会员页面。这样我们就可以轻松地将其传递到服务器。 -
现在打开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"));
});
};
保存所有文件,然后使用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