用Express Cookie解析器进行客户端认证
Cookie是可以存储的小块数据,或者在请求中被发送到网络浏览器。它们通常被用作一种存储介质。
例如,cookies可以用来保持客户的登录状态,存储用户的偏好,如语言、位置和其他跟踪信息。
Cookies以键-值对的形式存储在网络浏览器中。键作为客户数据的签名,或给予特定cookie的名称。值代表所需的具体数据。
在这篇文章中,我们将使用Express和cookie解析器建立一个交互式客户端认证应用。
前提条件
要跟上本教程,你需要。
- 对JavaScript编程语言的理解。
- 一个预装的IDE,最好是[Visual Studio Code]。
- 安装了[Node.js]。
- 对[Express.js]的理解。
教学目的
在本教程中,我们将使用Express cookie解析器库,学习使用网络浏览器cookie的客户端认证。
我们还将学习并在项目中应用以下部分。
- 网络浏览器cookie的概述。
- 不同的浏览器cookie属性。
- 使用cookie解析器库的客户端认证。
- 确保浏览器cookie免受攻击。
网络浏览器cookie的概述
网络浏览器的cookies可以在浏览器的DevTools中的应用程序存储标签下找到。
在设置cookie时,客户端和服务器端都很有用。具体来说,我们希望浏览器能始终记住一点信息。这就是为什么cookies相当有用。
每当请求set-cookie 头被附加到headers ,Cookies就会在浏览器中被设置。
这个set-cookie 头具有key-value 对的属性,其中key 代表name ,而value 是要设置的cookiedata 。
此外,让我们通过浏览https://example.com 来演示如何在客户端设置一个cookie。
在这个域名页面上,在任何地方点击右键,并打开控制台。
你可以通过在控制台中运行下面的代码来设置这个域的客户端的cookie。
document.cookie="example=domain"
key在上面的代码中,example ,而domain ,而value 。注意,这可以是你选择的任何东西。下面的图片是对cookie设置的描述。

浏览器cookie属性
Cookie是在每个客户端向服务器提出请求时发送的。每个域名作为一个桶,存储所有创建的cookies。
例如,当我们先前向域名example.com 发送请求时,没有任何cookie随之而来。但我们后来通过控制台注入了一个,这就填充了name ,和value 属性。
浏览器cookie有不同的属性,开发者可以加以利用。每一个属性都可以通过客户端注入控制台或在服务器上填写,只要有对后端的请求。
以下是cookies的一些属性。
1.名称
这是给保存在浏览器中的特定cookie的名称。这可以使用cookie数据中的key 。
2.值
在同一实例中,它存储了cookie数据的值。这是我们希望浏览器记住的主要信息。
3.域
它存储了客户端请求被发送到的任何URL。这就是向浏览器获取cookie数据的域名。
4.路径
它表示特定的路径或URL,如果请求的话,它将把cookie推送到浏览器。
5.过期
开发人员使用这个选项来设置cookie在客户端浏览器中持续的最长时间。这可以在服务器上设置,然后在发出请求时存储在客户端。
使用cookie解析器库的客户端认证
在本节中,我们将探讨如何使用cookie解析器库从后端验证客户端。
启动你的终端,执行以下命令来创建一些文件。
cd Desktop
mkdir project && cd project
touch server.js index.html
你导航到~/Desktop ,然后创建了一个project 目录并改到该目录。在这个文件夹中,你创建了2个文件,server.js 和index.html 。这些是服务器端的代码和客户端的HTML。
现在,你需要安装一些设置服务器所需的依赖项。从本地安装的node js ,你应该能够访问npm 库。
运行以下命令。
npm init
npm install express cookie-parser body-parser crypto --save
在你的代码编辑器中打开index.html ,和server.js 文件,添加以下代码。
<!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">
<title>Login Page</title>
</head>
<body>
<div>
<h1>Welcome: Kindly login to your bank account</h1>
<form action="/login" method="POST">
<div>
<label for="username">Username</label>
<input type="text" name="username"/>
</div>
<br><br>
<div>
<label for="passsword">Password</label>
<input type="password" name="password" id=""/>
</div>
<br><br>
<button type="submit">Submit</button>
</form>
</div>
</body>
</html>
这个HTML模板显示了用户输入其username ,和password 的表格页面。有一个用于提交数据的button ,还有一个欢迎词。
const express = require('express')
const bodyParser = require('body-parser');
const { createReadStream } = require('fs')
const app = express();
app.use(bodyParser.urlencoded({ extended: false })); // to parse the data sent by the client
// temporary database for users
const USERS = {
'user1': 'password1',
'user2': 'password2'
}
// hompage route
app.get('/', (req, res) => {
createReadStream('index.html').pipe(res);
})
// routing for the login page
app.post('/login', (req, res) => {
const username = req.body.username; // get username from the client form data
const password = USERS[username]
// only if the passwords are equal
if (password === req.body.password){
res.send('Logged in successfully!')
}
res.send('Failed to login!') // else condition
})
app.listen(process.env.port || 3000); // Server lisening to localhost and port 3000
在上面的代码中,我们设置了两个路由,都是get 和post 方法,分别用于主页和登录页面。
在get 方法下,我们读取从index.html 创建的流并将其发送到服务器上。
此外,post 方法检索客户端表单数据,并从临时数据库中检查输入密码的有效性。客户端将根据提供的条件得到一个响应。
打开浏览器,导航到localhost:3000,可以看到主页上的欢迎信息。

注意:如果输入的用户信息与后台临时数据库中的用户信息不同,那么登录尝试将失败。
我们需要利用cookie-parser 库,将客户端的用户名存储在浏览器的cookie中。
这个库允许服务器的响应和请求都使用cookie() 和cookies() 方法。
cookie() 方法可以在回调函数中对响应参数进行调用。它允许人们在浏览器cookie上保存数据。
cookies() 方法用于从浏览器中引用保存的cookie数据。
为了实现这些方法,在server.js 文件中添加以下代码。
const express = require('express')
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const { createReadStream } = require('fs')
const app = express();
app.use(bodyParser.urlencoded({ extended: false }))
app.use(cookieParser()); // initializing the lib
// temporary database
const USERS = {
'user1': 'password1',
'user2': 'password2'
}
const BALANCES = {
'user1': 500,
'user2': 1000
}
// routing for the homepage
app.get('/', (req, res) => {
const username = req.cookies.username; // getting stored username from the browser cookies
const balance = BALANCES[username];
// checks for the username if it exists
if (username) {
res.send(`Hi ${username}! Your balance is $${balance}.`);
}
else{
createReadStream('index.html').pipe(res);
}
})
// routing for the login page
app.post('/login', (req, res) => {
const username = req.body.username; // getting username from the client parsed data
const password = USERS[username]
// passwords check validity
if (password === req.body.password){
res.cookie('username', username); // storing username after passwords validity
res.send('Nice! You are successfully logged in.'); // response after
}
else{
res.send('Failed to log in!'); // if password checks fail
}
})
// routing for logout
app.get('/logout', (req, res) => {
res.clearCookie('username');
res.redirect('/')
})
app.listen(process.env.port || 3000); // Server lisening to localhost and port 3000
当你提交表单数据时,你应该得到密码检查通过后的响应。导航到localhost:3000,确认你是否真正登录了。
如果你正确地登录了,你应该看到与下面图片相同的页面。打开控制台,在cookie部分下检查保存的用户名。

出现的一个问题与cookie的安全性有关。入侵者可以轻易地将cookie数据编辑成其他内容。
确保浏览器cookie的安全
为了保证浏览器cookie的安全,我们将在每个请求上实现一个cookie的秘密。这个秘密将作为cookie的签名,为所有客户端对数据的请求签名。
我们可以做一个会话身份,而不是将用户名明明白白地存储在cookie中。也就是说,一个sessionId ,对每一个客户端来说都是不断变化的。
此外,每个会话身份将被存储在数据库中,一旦客户端注销或不再处于会话状态,就会被清除。
将server.js 中的代码替换为以下内容。
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const { createReadStream } = require('fs');
const { randomBytes } = require('crypto');
const COOKIE_SECRET = 'dashldhe128ewhgcvasdy7et2hvhwytt2';
const app = express();
app.use(bodyParser.urlencoded({ extended: false }))
app.use(cookieParser(COOKIE_SECRET));
// sessionID -> username
const SESSIONS = {}
// temporary database
const USERS = {
'user1': 'password1',
'user2': 'password2'
}
const BALANCES = {
'user1': 500,
'user2': 1000
}
// routing for the homepage
app.get('/', (req, res) => {
const sessionId = req.cookies.sessionId
// getting stored username from the browser cookies
const username = SESSIONS[sessionId];
// checks for the username if it exists
if (username) {
const balance = BALANCES[username];
res.send(`Hi ${username}! Your balance is ${balance}.`)
}
else{
createReadStream('index.html').pipe(res);
}
})
// routing for the login page
app.post('/login', (req, res) => {
const username = req.body.username;
const password = USERS[username]
if (password === req.body.password){
// getting the next sessionId from crypto lib
const nextSessionId = randomBytes(16).toString('base64')
// storing username after passwords validity
res.cookie('sessionId', nextSessionId);
SESSIONS[nextSessionId] = username;
res.redirect('/');
}
else{
// if password checks fail
res.send('Failed to log in!')
}
})
// routing for logout
app.get('/logout', (req, res) => {
const sessionId = req.cookies.sessionId;
// deleting the sessionId from temporary database
delete SESSIONS[sessionId];
// clearing the stored cookies sessionId
res.clearCookie('sessionId');
res.redirect('/');
})
// Server lisening to localhost and port 3000
app.listen(process.env.port || 3000);
为了测试实现,清除存储的cookies会话,然后在浏览器上导航到localhost:3000/login。
输入与存储在后台临时数据库中的数据相对应的客户端数据。当你在浏览器上打开浏览器的cookie时,你应该看到会话身份被正确存储。
让我们打开浏览器的隐身模式,登录到另一个用户。你会收到不同的会话身份,这些身份是不能改变的。
下面的图片显示了会话身份的一个实例。

结论
从网络浏览器的cookie中认证客户可能是复杂而耗时的。在本教程中,我们概述了如何使用cookie-parser库进行认证。
我们还讨论了浏览器cookie及其属性的概述,以及如何用cookie存储客户端数据。
最后,我们研究了如何通过用会话ID签署cookie数据来防止攻击。