本文是 Node Hero 系列教程的第八篇——通过这些章节,你将学会如何开始使用 Node.js 发布软件产品。
本篇 Passport.js 教程将带你一步步使用 Redis 搭建 Node.js 本地身份验证策略。你将学会如何使用 Passport.js 创建身份验证界面,用户在界面里提供用户名和密码。虽然看起来复杂,但是在 Node.js 中实现身份验证机制还是比较容易的。
本系列全部文章:
- Node Hero 系列之:开始使用 Node.js
- Node Hero 系列之:npm 教程
- Node Hero 系列之:Node.js 异步编程
- Node Hero 系列之:你的第一个 Node.js HTTP 服务器
- Node Hero 系列之:数据库教程
- Node Hero 系列之:Node.js request 模块教程
- Node Hero 系列之:Node.js 项目文件组织教程
- Node Hero 系列之:使用 Passport.js 做 Node.js 身份验证
- Node Hero 系列之:Node.js 单元测试教程
- Node Hero 系列之:Node.js 调试教程
- Node Hero 系列之:Node.js 安全教程
- Node Hero 系列之:如何使用 Heroku 或 Docker 部署 Node.js
- Node Hero 系列之:Node.js 应用监控
使用的技术
在一头扎进 Passport.js 身份验证教程之前,先让我们看看这章会用到的技术。
Passport.js 是什么?
- Passport.js 是一个简单的、非侵入式的 Node.js 身份验证中间件。
- Passport.js 可以集成到任何基于 Express.js 的 web 应用中。
Passport 是一个 Node.js 身份验证中间件,我们用它来做会话管理。
Redis 是什么?
- Redis 是一个开源的(BSD 开源协议)内存数据结构存储器,可用作数据库、缓存和消息代理。
- Redis 支持不同类型的抽象数据结构,例如字符串、哈希、列表、集合、带范围查询的有序集合、位图、hyperlog 以及带半径查询的地理空间索引。
我们会把用户会话信息保存在 Redis 而不是进程内存里。这样我们的应用更容易扩展。
需要身份验证的演示程序
为了演示,我们做一个只包含如下功能的应用:
- 有个登录表单
- 有两个受保护的页面:exposes two protected pages:
- 个人信息页
- 受保护的便签
项目结构
在前一篇的 Node Hero 系列教程中,你已经学过如何组织 Node.js 项目了,那就让我们学以致用吧!
我们将采用如下结构:
├── app
| ├── authentication
| ├── note
| ├── user
| ├── index.js
| └── layout.hbs
├── config
| └── index.js
├── index.js
└── package.json
如你所见,我们是根据功能来组织文件和目录的。我们将有一个用户页面,一个便签页面和一些身份验证相关的功能。
(下载完整源码: https://github.com/RisingStack/nodehero-authentication)
Node.js 身份验证流程
我们的目标是使用 Passport.js 在应用中实现如下的身份验证流程:
- 用户输入用户名和密码
- 应用检查用户名和密码是否匹配
- 如果匹配,应用会发送一个
Set-Cookie
头,用来验证后续页面 - 当用户访问相同域名的页面时,之前设置的 cookie 会加入到所有请求中
- 用该 cookie 验证受限的页面
使用 Passport.js 在 Node.js 应用中建立这样的身份验证策略,按照以下三个步骤:
步骤 1: 安装 Express
我们将使用 Express 作为服务框架——你可以在我们的Express 教程学习更多相关主题。
// 文件:app/index.js
const express = require('express')
const passport = require('passport')
const session = require('express-session')
const RedisStore = require('connect-redis')(session)
const app = express()
app.use(session({
store: new RedisStore({
url: config.redisStore.url
}),
secret: config.redisStore.secret,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
我们在这里做了什么?
首先,我们引入了会话管理所需的所有依赖。之后我们创建了一个express-session
实例,用来保存会话。
我们使用 Redis 做背后的存储,但是你也可以用其他工具,比如 MySQL 或者 MongoDB。
步骤 2:安装 Passport.js
Passport.js 是一个使用了插件的库的很好的例子。在这篇教程里,我们添加了 passport-local
模块,很容易通过用户名和密码集成简单的本地身份验证策略 。
为简单起见,在这个例子中我们没有使用另外的存储,只用到用户实例的内存。在实际应用中,findUser
将会在数据库里查找用户。
// 文件:app/authenticate/init.js
const passport = require('passport')
const bcrypt = require('bcrypt')
const LocalStrategy = require('passport-local').Strategy
const user = {
username: 'test-user',
passwordHash: 'bcrypt-hashed-password',
id: 1
}
passport.use(new LocalStrategy(
(username, password, done) => {
findUser(username, (err, user) => {
if (err) {
return done(err)
}
// 用户未找到
if (!user) {
return done(null, false)
}
// 一定要使用哈希后的密码和固定时间的比较算法
bcrypt.compare(password, user.passwordHash, (err, isValid) => {
if (err) {
return done(err)
}
if (!isValid) {
return done(null, false)
}
return done(null, user)
})
})
}
))
一旦 findUser
返回了用户对象,剩下的只需要比较用户哈希后的密码和真实密码,看是否匹配。一定要存储哈希后的密码,并且使用固定时间的比较算法以防时序攻击。
如果匹配,我们就让用户通过(返回用户对象给 Passport——return done(null, user)
),否则返回一个未授权错误(返回 null 给 Passport——return done(null)
)。
步骤 3:添加受保护端点
为了添加受保护端点,我们采用了 Express 用到的中间件模式。为此,我们先创建身份验证中间件:
// 文件:app/authentication/middleware.js
function authenticationMiddleware () {
return function (req, res, next) {
if (req.isAuthenticated()) {
return next()
}
res.redirect('/')
}
}
当用户通过验证时(有正确的 cookie),它只做一件事:调用下一个中间件。否则就重定向到登录页面。
使用它也很简单,只要在路由定义的地方加上中间件实例就行了。
// 文件:app/user/init.js
const passport = require('passport')
app.get('/profile', passport.authenticationMiddleware(), renderProfile)
总结:用 Passport.js 验证身份
在这篇 Passport.js 教程中,你学会了如何在 Node.js 应用中添加基本的身份验证。后续你可以扩展到其他身份验证策略,比如 Facebook 或 Twitter。你可以在 http://passportjs.org/ 找到更多策略。
完整可运行的例子的 Github 地址是 https://github.com/RisingStack/nodehero-authentication。
预告
Node Hero 的下一篇是关于 Node.js 单元测试的,你将学会单元测试、测试金字塔、测试替身等概念。
欢迎在评论区分享你的问题和反馈。