对于开发者来说,从头开始实施应用程序认证是一个非常令人头痛的问题。而且,如果实现得不正确,认证过程会导致系统出现漏洞。
在这篇文章中,我们将使用Passport库和MongoDB在Node.js应用程序中实现认证。
什么是Passport.js?
Passport是用于Node.js应用程序的一个流行的模块化认证中间件。有了它,认证可以轻松集成到任何基于Node和Express的应用程序中。Passport库提供了500多种认证机制,包括OAuth、JWT以及基于用户名和密码的简单认证。
使用Passport也可以很容易地将一种以上的认证类型集成到应用程序中。我们将在本文中使用mongoose-local 策略来实现认证。
创建Node应用程序的文件夹结构
首先,让我们为我们的文件创建特定的文件夹,像这样。

在这里,routes 文件夹包含所有路由的文件。views 文件夹包含将被显示的ejs 文件,而layout 文件夹包含ejs 布局代码。
除此之外,我们有一个.env 文件来存储密钥,一个index.js 文件作为应用程序的起点,还有一个userDetails.js 文件用于Mongoose模式。
用Passport、passport-local-mongoose 和MongoDB构建一个认证系统是非常简单的,但是在继续构建应用程序之前,我们需要一个MongoDB集群。
你可以使用你的自我托管版本的MongoDB,或者你可以使用MongoDB Atlas。无论哪种情况,都要先创建一个MongoDB数据库,并在.env 文件中存储SRV URI。
初始化Node并安装软件包
一旦我们完成了数据库的创建,让我们用npm初始化这个文件夹。创建一个新的文件夹,并用npm init -y 来初始化它。
接下来,安装依赖项。下面是它们的列表。
express:我们将为我们的网络应用程序使用Express框架mongoose:Node.js的MongoDB驱动将被用来连接MongoDBejs:我们的模板引擎express-ejs-layouts:这将被用于布局。dotenv:这个包将环境变量从一个名为.env的文件中加载到 。process.envconnect-ensure-login:这将保护需要认证的页面。passport和passport-local-mongoose: 用于实现认证express-session:创建和管理会话
安装这个包时要注意。
npm i express mongoose ejs express-ejs-layouts dotenv connect-ensure-login passport passport-local-mongoose express-session
我们将使用nodemon 开发依赖性。使用npm i -D nodemon 安装 dev 依赖关系,然后用这两行修改package.json 文件中的scripts 部分。
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
创建视图和布局
因为我们要使用ejs 作为模板引擎,所以我们要使用express-ejs-layouts 包来建立我们的默认布局。
尽管安装这个插件是可选的,但在处理一个大项目时,它很方便。首先,在根目录下创建一个名为views 的文件夹,然后在views 目录内创建一个名为layout 的文件夹。
在layout 目录内创建一个叫main.ejs 的文件。我在这个应用程序中使用BootstrapCSS来设计网页,所以我不需要写任何CSS。我不打算在这里解释HTML文件,因为它们非常简单明了,可以很容易理解。下面是main.ejs 文件的代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"></script>
<title>
<%- title %>
</title>
</head>
<body>
<%- body %>
</body>
</html>
在标题部分,我们已经使用CDN导入了Bootstrap的CSS和JavaScript。标题标签内的文本和正文将为每个视图而改变。
正因为如此,我们使用了<%- title %> 和<%- body %> 字样。我们将从我们的routes 文件中传递title ,而body 将渲染HTML主体。
这就是需要添加到main.ejs 文件中的所有内容。让我告诉你其他三个页面的代码。
1.index.ejs
<div class="px-4 py-5 my-5 text-center">
<img class="d-block mx-auto mb-4" src="https://uilogos.co/img/logomark/u-mark.png" alt="" width="auto" height="150">
<h1 class="display-5 fw-bold">Your Login Page</h1>
<div class="col-lg-6 mx-auto">
<p class="lead mb-4">Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quod, quidem. Distinctio,
natus, recusandae nostrum beatae provident aut quasi sequi eos nemo et quia dolor ipsum reprehenderit
molestiae id facere sunt.</p>
<div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
<a type="button" class="btn btn-primary btn-lg px-4 gap-3 me-2" href="/login">Log In</a>
</div>
</div>
</div>
这是我们渲染时的样子。

2.login.ejs
<div class="px-4 py-5 my-5 text-center">
<img class="d-block mx-auto mb-4" src="https://uilogos.co/img/logomark/u-mark.png" alt="" width="auto" height="150">
<h1 class="display-5 fw-bold">Login Here</h1>
<div class="col-lg-3 mx-auto">
<form action="/login" method="POST">
<div class="mb-2">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" name="username" placeholder="Username" required>
</div>
<div class="mb-2">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" name="password" placeholder="Password" required>
</div>
<button type="submit" class="btn btn-primary mt-2">Submit</button>
</form>
</div>
</div>
这里唯一需要注意的是,我们正在使用表单动作中的POST 方法打到login 路线。除此以外,它是一个简单的带有标签的HTML表单。登录页面将看起来像这样。

3.secret.ejs
<div class="px-4 py-5 my-5 text-center">
<img class="d-block mx-auto mb-4" src="https://uilogos.co/img/logomark/u-mark.png" alt="" width="auto" height="150">
<h1 class="display-5 fw-bold">Welcome to the Secret Page</h1>
<div class="col-lg-6 mx-auto">
<p class="lead mb-4">You've Successfully Entered the Secret Page</p>
</div>
<div class="d-grid gap-2 d-sm-flex justify-content-sm-center">
<a href="/logout" type="button" class="btn btn-danger btn-lg px-4 gap-3">Log Out</a>
</div>
</div>
秘密页面将看起来像这样。

这个页面包含一个名为logout 的按钮,它将注销用户。
然而,这些页面都还不会呈现,因为我们还没有设置好我们的服务器。现在让我们来做这件事。
设置服务器
让我们在index.js 文件中导入软件包。
// Requiring Modules
const express = require('express');
var expressLayouts = require('express-ejs-layouts');
const app = express();
// set up view engine and layout
app.use(expressLayouts);
app.set('layout', './layout/main');
app.set('view engine', 'ejs');
app.use(express.urlencoded({ extended: false }));
const PORT = process.env.PORT || 3000;
// Set up express server
const server = app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
在这里,我们导入express 和express-ejs-layouts 包。之后,我们用const app = express() 和app.use(expressLayouts) 来初始化express 和express-ejs-layouts 。
app.set('layout', './layout/main') 是将main 文件设置为布局,app.set('view engine', 'ejs') 是将ejs 设置为模板引擎。app.use(express.urlencoded({ extended: false })) 作为正文分析器工作。最后,我们正在创建我们的服务器,端口为3000 。
现在,让我们创建一个名为routes 的新文件夹,并在该文件夹内,创建一个名为router.js 的新文件。
const express = require('express');
const router = express.Router();
const connectEnsureLogin = require('connect-ensure-login');
const passport = require('passport');
// GET Routes
router.get('/', (req, res) => {
res.render('index', { title: 'Home' });
});
router.get('/login', (req, res) => {
res.render('login', { title: 'Login' });
});
router.get('/secret', connectEnsureLogin.ensureLoggedIn(), (req, res) =>
res.render('secret', { title: 'Secret Page' })
);
router.get('/logout', (req, res) => {
req.logout();
res.redirect('/');
});
// POST Routes
router.post(
'/login',
passport.authenticate('local', {
failureRedirect: '/login',
successRedirect: '/secret',
}),
(req, res) => {
console.log(req.user);
}
);
module.exports = router;
从上面的代码中可以看出,我们有三个GET 路线和一个POST 路线。首先,我们已经添加了必要的包。
secret 路由中的connectEnsureLogin.ensureLoggedIn() 中间件确保禁止用户在没有登录的情况下进入该页面。
在POST 路由内,passport.authenticate 中间件通过local 策略来验证用户,如果用户成功登录,它将重定向到secret 路由。否则,它将重定向到login 路由。
我们还通过title 变量传递页面的标题。req.logout() 是一个将用户注销的护照方法。最后,我们正在导出路由器。
用MongoDB设置用户模式
在根目录下创建一个名为userDetails.js 的新文件。
const mongoose = require('mongoose');
const passportLocalMongoose = require('passport-local-mongoose');
require('dotenv').config();
// Connecting Mongoose
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// Setting up the schema
const User = new mongoose.Schema({
username: String,
password: String,
});
// Setting up the passport plugin
User.plugin(passportLocalMongoose);
module.exports = mongoose.model('User', User);
我们需要mongoose ,以便与MongoDB连接,而passport-local-mongoose 使得将用户名和密码认证与MongoDB整合起来变得非常容易。
我们设置了dotenv 包,以使用下一行的环境变量。
然后,我们使用mongoose连接到数据库。User 变量是持有mongoose模式的。User.plugin(passportLocalMongoose) 方法为每个用户生成并存储数据库中的哈希值、盐和用户名。最后,我们导出模式。
我们几乎已经完成了。我们只需要设置我们的index.js 文件。
在Node应用程序中初始化Passport
让我们导入Passport 和express-session 模块,router.js ,以及userDetails.js 文件。然后,使用express-session 包设置会话。
const passport = require('passport');
const session = require('express-session');
const UserDetails = require('./userDetails');
const routes = require('./routes/router');
require('dotenv').config();
// Set up session
app.use(
session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: true,
})
);
secret 被存储在.env 文件中,它签署了会话ID cookie。如果resave 标志被设置为true ,会话数据将被强行存储。我们不希望这样,因为当saveUninitialized 设置为true 时,会强行保存未初始化的会话。你可以在这里详细阅读关于该包的信息。
现在,通过添加以下几行来设置Passport。
// Set up Passport
app.use(passport.initialize());
app.use(passport.session());
我们首先初始化Passport和会话认证中间件。一旦完成,我们必须设置本地认证。
passport.use(UserDetails.createStrategy());
passport.serializeUser(UserDetails.serializeUser());
passport.deserializeUser(UserDetails.deserializeUser());
上面的代码为我们的Node应用程序添加了本地认证。首先,我们通过调用createStrategy 方法在UserDetails 模型上设置本地策略。
然后,serializeUser 方法在认证时对传递的用户实例进行序列化,并在每一个后续请求中调用deserializeUser 实例,以解除对用户的序列化。
现在在你的index 文件中添加这段代码,并只运行一次index.js 。
UserDetails.register({username:'nemo', active: false}, '123');
以上一行将注册一个用户名为nemo ,密码为123 的用户。如果你现在检查你的MongoDB数据库,你会看到这个用户。
最后的index.js 文件将看起来像这样。
// Requiring Modules
const express = require('express');
var expressLayouts = require('express-ejs-layouts');
const app = express();
const passport = require('passport');
const session = require('express-session');
const UserDetails = require('./userDetails');
const routes = require('./routes/router');
require('dotenv').config();
// Set up view engine and layout
app.use(expressLayouts);
app.set('layout', './layout/main');
app.set('view engine', 'ejs');
// Set up session
app.use(
session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: true,
})
);
app.use(express.urlencoded({ extended: false }));
// Set up Passport
app.use(passport.initialize());
app.use(passport.session());
passport.use(UserDetails.createStrategy());
passport.serializeUser(UserDetails.serializeUser());
passport.deserializeUser(UserDetails.deserializeUser());
app.use(routes);
// Set up Express server
const server = app.listen(3000, () => {
console.log(`Listening on port ${server.address().port}`);
});
UserDetails.register({ username: 'nemo', active: false }, '123');
这就完成了认证。请看下面的GIF图,看看它的运行情况。

结论
认证是许多网络应用程序的一个重要组成部分。本文介绍了如何使用Passport库和MongoDB将认证集成到Node.js应用程序中。
你也可以查看Passport文档,了解在你的应用程序中实施的更多认证策略。我希望你喜欢阅读。完整的代码可以在这个GitHub repo中找到。
The postUsing Passport for authentication in Node.jsappeared first onLogRocket Blog.