nb:
- node.js的基本知识与本文一起遵循
- 从命令行shippet键入命令时,您应该在项目的根路径上
多年来,我从事的大多数代码库一直都喜欢使用JSON Web-Tokens(JWT)或身份验证-AS-AS-Service平台(Auth0,Okta等)来获得身份验证逻辑。
这些确实是绝佳的选择!但是,在较小的项目上,我发现这些总是过分的。最近,我开始使用Twitter Oauth API进行社交登录的Chrome扩展,并决定使用Passport.js将设置身份验证的一些繁重的工作外包。在下面,我浏览了Passport.js的身份验证
为什么
身份验证是创建软件的一个非常微妙的过程,并拥有适当的应用程序可以是一件繁琐的过程。为了不在 perfect 身份验证流的困境中陷入困境,可以使用数百万用户,我想坚持使用更简单的工具,这些工具可以有效地服务于我的最终用户,并且仅在规模上扩展需要出现(而不是以前的时刻)。对于像我正在处理的Chrome扩展名这样的前端重型应用程序,我发现使用Passport.js设置身份验证是我用例更有效的选择,因为我将很少使用后端。
什么是Passportjs
根据官方文档
护照是node.js的身份验证中间件。 Passport非常灵活且模块化,可以不显眼地放入任何基于Express的Web应用程序中。一组全面的策略支持使用用户名和密码,Facebook,Twitter等身份验证。
让我们分解一下,以便我们以更基本的层面理解它。
护照是node.js的身份验证中间件:中间件只是在请求/响应周期和node.js之间运行的函数,而node.js是一个运行时环境,使我们能够在浏览器外运行JavaScript 。
可以毫不客气地放入任何基于明确的应用程序中:这意味着我们可以在任何node.js应用程序中使用护照,以使我们的代码保持精益,清洁且易于维护。<<<<<<<<<<<<<< /p>
使用用户名和密码,Facebook,Twitter支持身份验证: Passport.js使您可以灵活地使用用户名/密码,Google,Twitter等对用户进行身份验证。它通过使用与NPM目录的单独软件包安装的strategies
来执行此操作,并且除了500 strategies外,还可以使用Passport!
最重要的是,这个包装是81kB
!对于上下文,是实施Google Oauth的官方库google-auth-library的大小为496kB
。很容易理解为什么我在此项目上使用Passport.js
项目范围和设置
不要浪费太多时间,让我们跳入项目的范围。
今天,我们将制作一个简单的应用程序
允许用户登录,查看当前时间并注销
首先是第一件事,让我们设置服务器。
( ps :您可能需要确保首先安装了nodejs)
创建文件夹
mkdir timely # create folder for project
cd timely # navigate into folder
touch index.js # create main entry point into your server
npm init -y # initialize npm
安装依赖项
npm install passport express express-session passport-local ejs # install dependencies
npm install nodemon --save-dev #install peer dependencies
设置用户界面
我们将使用EJS作为模板引擎,因此我们可以在网站上动态显示用户内容。如果您不太确定什么是模板引擎,则可以在这个惊人的article上了解一个想法。
mkdir views # create views folder
cd views # navigate into views folder
touch authenticate.ejs && touch index.ejs # create out home and authentication page
现在,我们将添加public
文件夹,该文件夹包含我们的静态资产
mkdir public # create public folder
cd public # navigate into public folder
touch main.js && touch styles.css
我包含了main.js
和styles.css
的内容,您可以通过here
找到index.ejs
文件,并将其内容替换为以下内容
<!-- navigate into timely/views/index.ejs and insert the following -->
<!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 rel="stylesheet" href="/styles.css" />
<script defer src="/main.js"></script>
<title>Timely | Who needs a rolex anyway 🤷♂️</title>
</head>
<body>
<div class="header">
<h1>Timely 🕰</h1>
</div>
<div class="container">
<h1>Welcome User</h1>
<div class="content"></div>
</div>
</body>
</html>
创建服务器
// Navigate into timely/index.js
const express = require('express');
const path = require('node:path');
const app = express();
app.use(express.static(path.join(__dirname, 'public'))); // Require static assets from public folder
app.set('views', path.join(__dirname, 'views')); // Set 'views' directory for any views being rendered res.render()
app.engine('html', require('ejs').renderFile); // Set view engine as EJS
app.set('view engine', 'ejs');
app.get('/', (req, res) => res.render('index'));
app.listen(3000, () => console.log('server is running on port 3000'));
package.json
中的设置脚本
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js",
"start:dev": "nodemon index.js"
}
}
运行脚本
npm run start:dev
在这一点
看起来很棒!但是我们只希望登录用户可以访问时间和日期。因此,让我们为未登录的用户创建一个身份验证页面。
在您的views
文件夹中找到authentication.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 rel="stylesheet" href="/styles.css" />
<title>Timely | Who needs a rolex anyway 🤷♂️</title>
</head>
<body>
<div class="header">
<h1>Timely 🕰</h1>
</div>
<form action="/log-in" method="POST" class="container">
<h1>please log in</h1>
<div>
<label for="username">Username</label>
<input name="username" placeholder="username" type="text" />
</div>
<div>
<label for="password">Password</label>
<input name="password" type="password" />
</div>
<button>Log In</button>
</form>
</body>
</html>
在我们编写更多代码之前,让我们了解我们安装的护照软件包以及它们如何合并
护照:这是主要护照模块,可帮助我们将用户记录的当前登录的信息附加到req.user
对象
Passport-Local: Passport.js与策略软件包合作,以执行应用程序所需的身份验证流。我们将使用passport-local
为此,因为我们将仅使用用户名和密码对用户进行身份验证
明确说明:这将使我们能够存储并管理服务器上登录的用户的会话
现在,我们对护照的工作原理有所了解,让我们创建护照中间件策略。这将是将用户信息添加到我们的req.user
对象的逻辑。在您的根文件夹中创建以下目录
mkdir strategy && cd strategy # create and move into strategy folder
touch local.js # create strategy file
nb:在现实世界项目上,最好在存储它们之前使用hash用户密码,以提高安全性,还可以使用实际数据库来存储用户信息。在这里,我将将用户信息存储在系统上的JSON文件中,以避免网络调用,并将文章的唯一重点保留在Passport.js。
上。
local.js
内部用以下内容替换其内容
const path = require('node:path'); // path module to find absolute paths on the system
const fs = require('node:fs'); // file system module to manipulate files
const passport = require('passport'); // the main star of the show
const LocalStrategy = require('passport-local'); // the co-protagonist in this sequel
const dbpath = path.join(__dirname, 'db.json'); // path to our custom in-house json database
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
if (!fs.existsSync(dbpath)) { // if db does not exist, create db
fs.writeFileSync(dbpath, JSON.stringify({ users: [] }));
}
const db = JSON.parse(fs.readFileSync(dbpath, { encoding: 'utf-8' }));
let user = db.users.find((item) => item.id === id);
if (!user) {
done(new Error('Failed to deserialize'));
}
done(null, user);
});
passport.use(
new LocalStrategy(async (username, password, done) => {
if (!fs.existsSync(dbpath)) { // if db.json does not exist yet, we create it
fs.writeFileSync(dbpath, JSON.stringify({ users: [] }));
}
const db = JSON.parse(fs.readFileSync(dbpath, { encoding: 'utf-8' }));
let user = db.users.find((item) => {
return item.username === username && item.password === password;
});
if (!user) {
user = {
id: Math.floor(Math.random() * 1000), // generate random id between numbers 1 - 999
username,
password,
};
db.users.push(user);
fs.writeFileSync(dbpath, JSON.stringify(db));
}
done(null, user);
})
);
每次向我们的服务器提出请求时,将运行此文件。让我们分解每种方法完全做什么
Passport.USE :这是第一个联系点,负责创建我们的用户。它是一次又一次地成功地验证我们的用户后运行的,它将此信息传递给下一个中间件,即passport.serializeUser
Passport.Serializeuser :这是在Passport中创建的,我们的密钥是user.id
。对于我们的sessionIDs
来说,避免冲突也很重要
passport.deserializeuser :在每个请求上,此方法接收我们的sessionID
并扫描我们的数据库以获取当前登录的用户的用户信息,然后将该信息附加到req.user
对象
现在,我们知道护照的工作原理,让它最终将其包括在项目中,这将使我们能够识别登录用户并相应地重新调整他们的请求
const express = require('express');
const passport = require('passport');
const session = require('express-session');
const path = require('node:path');
const app = express();
require('./strategy/local'); // passport strategy will listen to every request
app.use(
session({
secret: 'SOME SECRET', // secret key to sign our session
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24, // TTL for the session
},
})
);
// initialize passport package
app.use(passport.initialize());
// initialize a session with passport that authenticates the sessions from express-session
app.use(passport.session());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public'))); // Require static assets from public folder
app.set('views', path.join(__dirname, 'views')); // Set 'views' directory for any views being rendered res.render()
app.engine('html', require('ejs').renderFile); // Set view engine as EJS
app.set('view engine', 'ejs');
app.get('/', (req, res) => {
if (!req.user) {
// if passport does not have record of this user, we redirect them to the authentication page
res.render('authenticate');
} else {
res.render('index', { user: req.user });
}
});
app.post(
'/log-in',
passport.authenticate('local', {
// on Initial login, passport will redirect to either of these routes
successRedirect: '/',
failureRedirect: '/',
})
);
app.listen(3000, () => console.log('server is running on port 3000'));
如果我们再次在浏览器上访问http://localhost:3000/
,我们将立即路由到身份验证页面。
注册后,我们将分配一个会话cookie,您可以在我们的浏览器devtools控制台中查看该会话(右键单击页面 - > Inspect-> Application-> Application-> Cookie-> http://localhost:3000)。
每次将请求发送到服务器时,此cookie都会与该饼干一起发送,并且从此cookie中发送,Passport可以正确识别我们是否已登录。
为了进一步证明这一点,您可以继续从DevTools控制台中删除Cookie,然后刷新页面 - 瞬间,您立即将其重新列入身份验证页面。
我们的主页看起来不错,但让我们通过使用ejs
显示用户用户名以及用户注销的能力来增加一些个性。
在index.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 rel="stylesheet" href="/styles.css" />
<script defer src="/main.js"></script>
<title>Timely | Who needs a rolex anyway 🤷♂️</title>
</head>
<body>
<div class="header">
<h1>Timely 🕰</h1>
</div>
<form class="container" action="/log-out" method="post">
<h1>Welcome back, <%= user.username %></h1>
<div class="content"></div>
<button>Log out</button>
</form>
</body>
</html>
现在要处理服务器上的注销路由,我们可以在app.listen()
方法上方添加以下代码
app.post('/log-out', (req, res) => {
req.logOut((err) => { // clear out information in passport-session and redirects user
if (err) {
res.send('something went wrong');
}
res.redirect('/');
});
});
app.use('*', (req, res) => res.redirect('/')); // to catch and redirect all other routes
nb:您可能已经注意到,每次更改服务器文件时,都会重新列入身份验证页面。这是因为
sessionIDs
存储在由express-session
创建的缓存中,一旦nodemon
重新启动了服务器,express-session
缓存被擦拭干净,因此,护照无法使用以前的sessionID
识别我们。
就是这样!现在,我们可以使用Passport.js充分登录和退出应用程序。
我确实建议阅读更多有关他们的documentations的信息,以发现您可以在未来项目中使用的新策略。直到下次,Ciao!