Chinese (Simplified) (中文(简体)) translation by Fuhuan (you can also view the original English article)
为任何应用程序实现身份验证功能都是一项艰巨的任务,Node.js 应用程序也不例外。
在本教程中,我们将从头开发一个 Node.js 应用程序并使用一个相对较新但非常流行的身份验证插件 - Passport 来处理我们的身份验证问题。
Passport 的文档将其描述为 “Node 的简单,不显眼的身份验证插件”,这是正确的。
通过将自身作为插件提供,Passport 在将 Web 应用程序的其他问题与其身份验证需求分离方面做得非常出色。 它允许将 Passport 轻松配置到任何基于 Express 的 Web 应用程序中,就像我们配置其他 Express 插件一样,例如日志记录,正文解析,cookie 解析,会话处理等。
本教程假设你对 Node.js 和 Express 框架有基本的了解,并尝试将注意力集中在身份验证上,尽管我们从头开始创建示例 Express 应用程序并通过向其添加路由并验证其中一些路由信息来进行。
认证策略
Passport 为我们提供了 140 多种身份验证机制可供选择。 你可以针对本地 / 远程数据库实例进行身份验证,或使用 OAuth 提供程序对 Facebook,Twitter,Google 等进行单点登录,以对你的社交媒体帐户进行身份验证,或者你可以从支持身份验证的广泛提供商列表中进行选择使用 Passport 并为其提供节点模块。
但不要担心:你不需要包含应用程序不需要的任何策略 / 机制。 所有这些策略是相互独立的,并打包为单独的节点模块,默认情况下,在安装 Passport 的插件时不包括这些模块:npm install passport
在本教程中,我们将使用 Passport 的本地身份验证策略,并针对本地配置的 Mongo 数据库实例对用户进行身份验证,并将用户详细信息存储在数据库中。 要使用本地身份验证策略,我们需要安装 passport-local 模块:npm install passport-local
但是:在启动终端并开始执行这些命令之前,让我们从头开始构建一个 Express 应用程序并添加一些路由(用于登录,注册和主页),然后尝试添加我们的身份验证插件。 请注意,我们将在本教程中使用 Express 4,但稍微有些不同, Passport 也可以与 Express 3 一起使用。
设置应用程序
如果你还没有,那么请继续安装 Express&express-generator,通过在终端上执行 express passport-mongo 来生成样板应用程序。 生成的应用程序结构应如下所示:

让我们删除一些我们不会使用的默认功能 - 继续并删除 users.js 路由并从 app.js 文件中删除它的引用。
添加项目依赖项
打开 package.json 并添加护照和本地护照模块的依赖项。
"passport": "~0.2.0", "passport-local": "~1.0.0"
由于我们将在 MongoDB 中保存用户详细信息,因此我们将使用 Mongoose 作为对象数据建模工具。 将依赖项安装并保存到 package.json 的另一种方法是输入:
npm install mongoose --save
package.json 应如下所示:

现在,安装所有依赖项并通过执行 npm install && npm start 运行样板应用程序。 它现在将下载并安装所有依赖项,并将启动节点服务器。 你可以在 http:// localhost:3000 / 查看基本的 Express 应用程序,但现在没有什么看头。
很快,我们将通过创建一个完整的应用程序来改变这一点,该应用程序要求显示新用户的注册页面,注册用户的登录,并使用 Passport 对注册用户进行身份验证。
创建 Mongoose 模型
由于我们将在 Mongo 中保存用户详细信息,因此我们在 Mongoose 中创建一个用户模型,并将其保存在我们的应用程序中的 models / user.js 中。
var mongoose = require('mongoose'); module.exports = mongoose.model('User',{ username: String, password: String, email: String, gender: String, address: String });
基本上,我们正在创建一个 Mongoose 模型,使用它我们可以在底层数据库上执行 CRUD 操作
配置 Mongo
如果你没有在本地安装 Mongo,我们建议你使用云数据库服务,例如 Modulus 或 MongoLab。 使用这些实现创建一个有效的 MongoDB 实例不仅是免费的,而且只需点击几下即可。
在其中一个服务上创建数据库后,它将为你提供数据库 URI,如 mongodb:// : @ novus.modulusmongo.net:27017 / ,可用于执行 CRUD 操作在数据库上。 最好将数据库配置保存在一个单独的文件中,该文件可以在需要时启动。 因此,我们创建一个节点模块 db.js,它看起来像:
module.exports = { 'url' : 'mongodb://<dbuser>:<dbpassword>@novus.modulusmongo.net:27017/<dbName>' }
如果你像我一样,你正在使用本地 Mongo 实例,那么可以启动 mongod 守护进程了,db.js 应该看起来像
module.exports = { 'url' : 'mongodb://localhost/passport' }
现在我们在 app.js 中使用此配置并使用 Mongoose API 连接到它:
var dbConfig = require('./db.js'); var mongoose = require('mongoose'); mongoose.connect(dbConfig.url);
配置 Passport
Passport 只提供了处理身份验证的机制,留下了自己实现会话处理的责任,为此我们将使用快速会话。 在配置路由之前打开 app.js 并粘贴下面的代码:
// Configuring Passport var passport = require('passport'); var expressSession = require('express-session'); app.use(expressSession({secret: 'mySecretKey'})); app.use(passport.initialize()); app.use(passport.session());
这是必要的,因为我们希望我们的用户会话本质上是持久的。 在运行应用程序之前,我们需要安装 express-session 并将其添加到 package.json 中的依赖项列表中。 要做到这一点,请键入 npm install --save express-session
序列化和反序列化用户实例
Passport 还需要从会话存储中序列化和反序列化用户实例以支持登录会话,以便每个后续请求都不包含用户凭据。 它为此提供了两个方法 serializeUser 和 deserializeUser:
passport.serializeUser(function(user, done) { done(null, user._id); }); passport.deserializeUser(function(id, done) { User.findById(id, function(err, user) { done(err, user); }); });
使用 Passport 策略
我们现在将定义 Passport 处理登录和注册的策略。 它们中的每一个都是 Passport 的本地身份验证策略的实例,并且将使用 passport.use()创建 功能。 我们使用 connect-flash 通过提供可以在出错时显示给用户的 flash 消息来帮助我们处理错误。
登录策略
登录策略如下所示:
// passport/login.js passport.use('login', new LocalStrategy({ passReqToCallback : true }, function(req, username, password, done) { // check in mongo if a user with username exists or not User.findOne({ 'username' : username }, function(err, user) { // In case of any error, return using the done method if (err) return done(err); // Username does not exist, log error & redirect back if (!user){ console.log('User Not Found with username '+username); return done(null, false, req.flash('message', 'User Not found.')); } // User exists but wrong password, log the error if (!isValidPassword(user, password)){ console.log('Invalid Password'); return done(null, false, req.flash('message', 'Invalid Password')); } // User and password both match, return user from // done method which will be treated like success return done(null, user); } ); }));
passport.use()的第一个参数是策略的名称,该策略将在以后应用时用于标识此策略。 第二个参数是你要创建的策略类型,这里我们使用用户名密码或 LocalStrategy。 需要注意的是,默认情况下,LocalStrategy 希望在用户名和密码参数中找到用户凭据,但它允许我们使用任何其他命名参数。 所述 passReqToCallback 配置变量允许我们访问的请求的回叫对象,从而使我们能够使用与该请求相关联的任何参数。
接下来,我们使用 Mongoose API 在我们的基础用户集合中查找用户,以检查用户是否是有效用户。 我们的回调中的最后一个参数:done 表示一个有用的方法,使用它可以向 Passport 模块发出成功或失败的信号。 要指定失败,第一个参数应该包含错误,或者第二个参数应该计算为 false。 由于密码本质上是弱的,我们应该在将它们保存到数据库之前对它们进行加密。
由于密码本质上是弱的,我们应该在将它们保存到数据库之前对它们进行加密。 为此,我们使用 bcrypt-nodejs 来帮助我们加密和解密密码。
var isValidPassword = function(user, password){ return bCrypt.compareSync(password, user.password); }
如果你对代码片段感到不安并希望看到完整的代码,请随时在此处浏览代码。
注册策略
现在,我们定义下一个策略,该策略将处理新用户的注册并在我们的基础 Mongo DB 中创建他或她的条目:
passport.use('signup', new LocalStrategy({ passReqToCallback : true }, function(req, username, password, done) { findOrCreateUser = function(){ // find a user in Mongo with provided username User.findOne({'username':username},function(err, user) { // In case of any error return if (err){ console.log('Error in SignUp: '+err); return done(err); } // already exists if (user) { console.log('User already exists'); return done(null, false, req.flash('message','User Already Exists')); } else { // if there is no user with that email // create the user var newUser = new User(); // set the user's local credentials newUser.username = username; newUser.password = createHash(password); newUser.email = req.param('email'); newUser.firstName = req.param('firstName'); newUser.lastName = req.param('lastName'); // save the user newUser.save(function(err) { if (err){ console.log('Error in Saving user: '+err); throw err; } console.log('User Registration succesful'); return done(null, newUser); }); } }); }; // Delay the execution of findOrCreateUser and execute // the method in the next tick of the event loop process.nextTick(findOrCreateUser); }); );
在这里,我们再次使用 Mongoose API 来查找是否已存在具有给定用户名的任何用户。 如果没有,则创建一个新用户并将用户信息保存在 Mongo 中。 否则使用完成回调和 flash 消息返回错误。 请注意,我们使用 bcrypt-nodejs 在保存之前创建密码的哈希:
// Generates hash using bCrypt var createHash = function(password){ return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null); }
创建路由
如果我们看到我们的应用程序的鸟瞰图,它看起来像:

我们现在在以下模块中定义应用程序的路由,该模块采用在上面的 app.js 中创建的 Passport 实例。 将此模块保存在 routes / index.js 中
module.exports = function(passport){ /* GET login page. */ router.get('/', function(req, res) { // Display the Login page with any flash message, if any res.render('index', { message: req.flash('message') }); }); /* Handle Login POST */ router.post('/login', passport.authenticate('login', { successRedirect: '/home', failureRedirect: '/', failureFlash : true })); /* GET Registration Page */ router.get('/signup', function(req, res){ res.render('register',{message: req.flash('message')}); }); /* Handle Registration POST */ router.post('/signup', passport.authenticate('signup', { successRedirect: '/home', failureRedirect: '/signup', failureFlash : true })); return router; }
上面代码片段中最重要的部分是使用 passport.authenticate() 在分别对 / login 和 / signup 路由进行 HTTP POST 时,将身份验证委派给登录和注册策略。 请注意,不必在路径路径上命名策略,并且可以将其命名为任何名称。
创建主视图
接下来,我们为应用程序创建以下两个视图:
- layout.jade 包含基本布局和样式信息
- index.jade 包含登录页面,其中包含登录表单并提供创建新帐户的选项
extends layout block content div.container div.row div.col-sm-6.col-md-4.col-md-offset-4 h1.text-center.login-title Sign in to our Passport app div.account-wall img(class='profile-img', src='https://lh5.googleusercontent.com/-b0-k99FZlyE/AAAAAAAAAAI/AAAAAAAAAAA/eu7opA4byxI/photo.jpg?sz=120') form(class='form-signin', action='/login', method='POST') input(type='text', name='username' class='form-control', placeholder='Email',required, autofocus) input(type='password', name='password' class='form-control', placeholder='Password', required) button(class='btn btn-lg btn-primary btn-block', type='submit') Sign in span.clearfix a(href='/signup', class='text-center new-account') Create an account #message if message h1.text-center.error-message #{message}
感谢 Bootstrap,我们的登录页面现在看起来像

我们还需要两个注册详细信息和应用程序主页的视图:
- register.jade 包含注册表单
- home.jade 说你好,并显示登录用户的详细信息
如果你不熟悉 Jade,请查看文档。
实现注销功能
作为插件的 Passport 被允许在请求和响应对象上添加某些属性和方法,并通过添加非常方便的 request.logout()来正确使用它。 使用户会话与其他属性无效的方法。
/* Handle Logout */ router.get('/signout', function(req, res) { req.logout(); res.redirect('/'); });
保护路线
Passport 还能够保护对被认为不适合匿名用户的路由的访问。 这意味着如果某个用户尝试访问 http:// localhost:3000 / home 而未在应用程序中进行身份验证,则他将通过执行重定向到主页
/* GET Home Page */ router.get('/home', isAuthenticated, function(req, res){ res.render('home', { user: req.user }); }); // As with any middleware it is quintessential to call next() // if the user is authenticated var isAuthenticated = function (req, res, next) { if (req.isAuthenticated()) return next(); res.redirect('/'); }
结论
Passport 并不是这个领域中唯一一个认证 Node.js 应用程序的插件,而且还有像 EveryAuth 这样的替代品,但模块化,灵活性,社区支持以及它只是一个插件使 Passport 绝对是一个更好的选择。
关于两者之间的详细比较,这是 Passport 自己的开发人员的一个有趣的看法。
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post