如何使用Express Cookie解析器的客户端认证

139 阅读6分钟

用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设置的描述。

Example 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.jsindex.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

在上面的代码中,我们设置了两个路由,都是getpost 方法,分别用于主页登录页面

get 方法下,我们读取从index.html 创建的流并将其发送到服务器上。

此外,post 方法检索客户端表单数据,并从临时数据库中检查输入密码的有效性。客户端将根据提供的条件得到一个响应。

打开浏览器,导航到localhost:3000,可以看到主页上欢迎信息

Login Page

注意:如果输入的用户信息与后台临时数据库中的用户信息不同,那么登录尝试将失败。

我们需要利用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部分下检查保存的用户名。

Succesful Login Page

出现的一个问题与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时,你应该看到会话身份被正确存储。

让我们打开浏览器的隐身模式,登录到另一个用户。你会收到不同的会话身份,这些身份是不能改变的。

下面的图片显示了会话身份的一个实例。

Session Page

结论

从网络浏览器的cookie中认证客户可能是复杂而耗时的。在本教程中,我们概述了如何使用cookie-parser库进行认证。

我们还讨论了浏览器cookie及其属性的概述,以及如何用cookie存储客户端数据。

最后,我们研究了如何通过用会话ID签署cookie数据来防止攻击。