了解Cookie并在Node.js中实现它们

259 阅读16分钟

了解Cookie并在Node.js中实现它

网站通常会在浏览器上存储少量的数据。基于浏览器的存储主要有三种:会话存储、本地存储和cookie存储。本指南将讨论什么是cookie,它如何工作,以及如何在Node.js应用程序中使用HTTP cookie。

前提条件

  • [Node.js]的基本知识
  • 在你的电脑上[安装了Node.js]。
  • 对 [如何使用Express创建一个HTTP服务器]有基本了解。

什么是cookie?

Cookie通常是一个存储在你的网络浏览器中的微小文本文件。Cookie最初是用来存储你所访问的网站的信息的。但随着技术的进步,cookie可以跟踪你的网络活动并检索你的内容偏好。我相信,在某些时候,你已经看到一个类似于下面所示的弹出屏幕。

Accept cookies website popup

这将有助于你访问过的网站更多了解你,并定制你未来的体验。

比如说。

  • Cookies保存了你的语言偏好。这样,当你将来访问该网站时,你使用的语言将被记住。

  • 你很可能已经访问过一个电子商务网站。当你把物品纳入购物车时,一个cookie会记住你的选择。每当你再次访问该网站时,你的购物清单项目仍会在那里。基本上,cookie是用来记住用户的数据。

因此,cookie是包含键值对信息的小字符串,从网络服务器发送至浏览器,以获得关于用户的信息。然后,浏览器将把它们保存在本地。这样,就可以根据用户以前向服务器提出的请求,向服务器提出后续请求,立即更新网站上的用户内容。Cookie是由HTTP生成的;因此,称为HTTP Cookie。

Cookie的简要历史

第一个HTTP cookie是在[1994年由Lou Montulli]创造的,他是[Netscape Communications](创建Netscape浏览器的公司)的一名雇员。Lou当时正在为一家公司创建一个网上商店,该公司声称他们的服务器因储存每个用户的购物车数据而变得很满。

因此,Lou不得不想办法将购物车的内容储存在本地。他想出了一个主意,把购物车的信息保存在用户的电脑上,以节省服务器空间。他借用了HTTP cookie的概念,这个概念来自于一个被称为 "[神奇的cookie]"的计算机令牌,它被用来在登录系统时识别用户。

卢重新创造了这个概念,并在网络浏览器中实现了它。1994年,网景浏览器实现了cookie,随后,IE浏览器于1995年实现了cookie,这标志着HTTP cookie的诞生。

Cookie是如何工作的

当用户第一次访问一个支持cookie的网站时,浏览器会提示用户该网页使用cookie,并要求用户接受cookie保存在他们的电脑上。通常情况下,当一个提出用户请求时,服务器会通过发回一个cookie(在许多其他方面)来回应。

这个cookie将被储存在用户的浏览器中。当用户访问网站或发送另一个请求时,该请求将与cookie一起被送回。该cookie将有关于用户的某些信息,服务器可以利用这些信息对任何其他后续请求做出决定。

一个完美的例子是通过浏览器访问Facebook。当你想访问你的Facebook账户时,你必须用正确的凭证登录,以获得适当的访问权。但在这种情况下,如果每次都连续登录Facebook,会让人感到厌烦。

当你第一次提出登录请求,服务器验证了你的凭证后,服务器将发送你的Facebook账户内容。它还会向你的浏览器发送cookies。然后,这些cookie会储存在你的电脑上,并在你每次向该网站提出请求时提交给服务器。一个cookie将被保存为该用户特有的标识符。

当你再次访问Facebook时,你提出的请求、保存的cookie和服务器将跟踪你的登录会话,并记住你是谁,从而使你保持登录状态。

不同类型的cookie包括。

  • 会话cookie - 在短时间内存储用户的信息。当当前会话结束时,该会话cookie将从用户的计算机中删除。

  • 持久cookie - 持久cookie缺乏过期日期。只要网络服务器管理员设置了它,它就会被保存。

  • 安全cookies - 由加密的网站使用,以提供保护,防止任何可能来自黑客的威胁。

  • 第三方cookies - 由在其网页上显示广告或跟踪网站流量的网站使用。它们允许外部方访问,以根据用户以前的偏好决定显示的广告类型。

会话和cookie之间的主要区别

会话和cookies之间的主要区别是,会话存在于服务器端(网络服务器),而cookies存在于客户端(用户浏览器)。会话有敏感信息,如用户名和密码。这就是为什么它们被存储在服务器上。会话可以用来识别和验证哪个用户在提出请求。

正如我们所解释的,cookies存储在浏览器中,其中不能存储敏感信息。它们通常用于保存用户的偏好。

用Node.js设置cookies

让我们深入研究,看看我们如何使用Node.js实现cookie。我们将在浏览器中创建和保存一个cookie,更新和删除一个cookie。

继续,在你的电脑上创建一个项目目录。使用npm init -y 来初始化Node.js,生成一个package.json 文件来管理Node.js项目的依赖性。

我们将使用以下NPM包。

  • Express- 这是一个用于Node.js的意见服务器端框架,可以帮助你创建和管理HTTP服务器REST端点。

  • cookie-parser- cookie-parser查看客户端和服务器交易之间的头信息,读取这些头信息,解析出正在发送的cookies,并将其保存在浏览器中。换句话说,cookie-parser将帮助我们根据用户向服务器提出的请求来创建和管理cookies。

运行下面的命令来安装这些NPM 包。

npm install express cookie-parser

我们将创建一个简单的例子来演示cookie如何工作。

第1步--导入已安装的软件包

为了建立一个服务器并保存cookie,请将cookie解析器和Express模块导入你的项目。这将使必要的函数和对象可以被访问。

const express = require('express')
const cookieParser = require('cookie-parser')

第2步 让你的应用程序使用这些软件包

你需要在你的应用程序中使用上述模块作为中间件,如下图所示。

//setup express app
const app = express()

// let’s you use the cookieParser in your application
app.use(cookieParser());

这将使你的应用程序使用cookie解析器和Express模块。

第3步 设置一个简单的路由来启动服务器

我们使用下面的代码来为主页设置一个路由。

//set a simple for homepage route
app.get('/', (req, res) => {
    res.send('welcome to a simple HTTP cookie server');
});

第4步 - 设置一个端口号

这是服务器在运行时应该监听的端口号。这将帮助我们在本地访问我们的服务器。在这个例子中,服务器将监听端口8000 ,如下图所示。

//server listening to port 8000
app.listen(8000, () => console.log('The server is running port 8000...'));

现在我们有一个简单的服务器设置。运行node app.js 来测试它是否工作。

Running an Express server

而如果你访问8000端口的localhost (http://localhost:8000/),你应该得到一个由服务器发送的HTTP响应。现在我们准备开始实现cookies了。

设置cookie

让我们添加routesendpoints ,这将帮助我们创建、更新和删除一个cookie。

第1步 - 设置一个cookie

我们将设置一个路线,在浏览器中保存一个cookie。在这种情况下,cookie将从服务器传到客户端的浏览器。要做到这一点,请使用res 对象,并将cookie 作为方法,即res.cookie() ,如下图所示。

//a get route for adding a cookie
app.get('/setcookie', (req, res) => {
    res.cookie(`Cookie token name`,`encrypted cookie string Value`);
    res.send('Cookie have been saved successfully');
});

当上述路由从浏览器执行时,客户端会向服务器发送一个获取请求。但在这种情况下,服务器会用一个cookie来响应,并将其保存在浏览器中。

继续运行node app.js ,为上述端点提供服务。打开http://localhost:8000/getcookie 你的浏览器并访问该路由。

要确认cookie已被保存,请到浏览器的检查器工具🡆选择应用标签🡆cookie🡆选择你的域名URL。

Saving a cookie in the browser

第2步 - 使用req.cookies方法检查保存的cookie

如果服务器向浏览器发送这个cookie,这意味着我们可以通过req.cookies 迭代传入的请求,并检查是否存在保存的cookie。你可以把这个cookie记录到控制台,或者把cookie请求作为一个响应发送给浏览器。让我们来做这件事。

// get the cookie incoming request
app.get('/getcookie', (req, res) => {
    //show the saved cookies
    console.log(req.cookies)
    res.send(req.cookies);
});

再次使用node app.js 运行服务器,暴露上述路由(http://localhost:8000/getcookie),你可以在浏览器上看到响应。

A saved cookie

以及你的控制台日志上。

Cookie saved in the console

第3步 - 安全cookies

在设置cookie时,你应该始终采取的一项预防措施是安全。在上面的例子中,这个cookie可以被认为是不安全的。

例如,你可以在浏览器控制台使用JavaScript访问这个cookie (document.cookie)。这意味着,这个cookie是暴露的,可以通过跨站脚本来利用。

当你打开浏览器检查器工具并在控制台执行以下内容时,你可以看到这个cookie。

document.cookie

通过浏览器控制台可以看到保存的cookie值。

Browser console

作为预防措施,你应该始终尝试使用JavaScript使你的cookie在客户端无法访问。

我们可以添加几个属性来使这个cookie更安全。

  • HTTPonly 确保一个cookie不能用JavaScript代码访问。这是对交叉脚本攻击的最关键的保护形式。
  • secure 属性确保浏览器将拒绝cookie,除非连接发生在HTTPS上。
  • sameSite 该属性提高了cookie的安全性,避免了隐私泄露。

默认情况下,sameSite 最初被设置为none (sameSite = None)。这允许第三方在各个网站上跟踪用户。目前,它被设置为Lax (sameSite = Lax),这意味着只有当浏览器的URL中的域与cookie的域相匹配时才会设置cookie,从而消除了第三方的域。sameSite 也可以被设置为Strict (sameSite = Strict)。这将限制跨站共享,甚至在同一出版商拥有的不同域名之间。

  • 你还可以添加你希望cookie在用户浏览器上可用的最长时间。当设定的时间过后,该cookie将自动从浏览器中删除。
//a get route for adding a cookie
app.get('/setcookie', (req, res) => {
    res.cookie(`Cookie token name`,`encrypted cookie string Value`,{
        maxAge: 5000,
        // expires works the same as the maxAge
        expires: new Date('01 12 2021'),
        secure: true,
        httpOnly: true,
        sameSite: 'lax'
    });
    res.send('Cookie have been saved successfully');
});

在这种情况下,我们正在访问localhost上的服务器,它使用了一个非HTTPS的安全源。为了测试服务器,你可以设置secure: false 。然而,当你希望在HTTPS安全原点上创建cookie时,请始终使用true 值。

如果你再次运行服务器(node app.js),并在浏览器上导航到http://localhost:8000/setcookie ,你可以看到cookie的值已经用安全值更新。

Cookies updated security values

此外,你不能使用JavaScript访问该cookie,即document.cookie

Cookie not accessed with JavaScript

第4步 - 删除一个cookie

通常情况下,可以根据用户提出的要求,从浏览器中删除cookie。例如,如果cookie被用于登录目的,当用户决定注销时,该请求应伴随着删除命令。

下面是我们如何在这个例子中删除我们上面设置的cookie。使用res.clearCookie() ,清除所有的cookie。

// delete the saved cookie
app.get('/deletecookie', (req, res) => {
    //show the saved cookies
    res.clearCookie()
    res.send('Cookie has been deleted successfully');
});

打开http://localhost:8000/deletecookie ,你会看到保存的cookie已经被删除。

A saved cookie deleted

一个用例:如何用cookie来验证一个用户

让我们有一个简单的认证例子,当用户第一次登录网站时,使用cookie来授予访问权。这个例子将告诉你如何使用cookie来处理一个简单的认证。

第1步 - 项目结构

创建一个新的项目文件夹,用npm init -y ,初始化Node.js 项目。设置你的项目,如下面这个结构所示。

└───node-js-auth-cookies
    ├───public
    │ styles.css
    │
    └───src
        │ index.js
        │
        └───views
                home.ejs
                login.ejs
                welcome.ejs

第2步 设置项目的依赖性

这个项目将使用以下NPM包。

  • Cookie-parser- 用于解析从响应对象到请求对象的cookies。
  • Express- 用于Node.js的Web框架
  • Ejs- 用于渲染视图。
  • Helmet- 用于安全的HTTP请求。
  • Nodemon- 用于在开发过程中重新启动服务器。

使用下面的命令安装上述软件包。

npm i --save cookie-parser express ejs helmet nodemon

由于我们正在使用nodemon 来观察服务器文件,所以请继续修改你的package.json 文件中的scripts 标签,如下所示。

"scripts": {
    "dev": "nodemon src/index.js",
    "start": "node src/index.js"
}

这将有助于我们用npm run dev 来启动服务器,而nodemon 将监视我们的文件。每当你做出并保存更改,它就会重新启动服务器。

第3步 - 设置服务器

在这一步,我们将设置我们的express 服务器。在代码的不同行内包含的注释将帮助你在遇到困难时弄清问题。

// src/index.js

// import all the modules/packages
const express = require("express");
const path = require("path");
const helmet = require("helmet");
const cookieparser = require("cookie-parser");

// allow the app to use express
const app = express();

// allow the app to use cookieparser
app.use(helmet());

// allow the app to use cookieparser
app.use(cookieparser());

// allow the express server to process POST request rendered by the ejs files 
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// allow the express server to read and render the static css file
app.use(express.static(path.join(__dirname, "..", "public")));
app.set("view engine", "ejs");

// render the ejs views
app.set("views", path.join(__dirname, "views"));

// a port number to expose the server
const PORT = process.env.PORT || 4000;

app.get("/", (req, res) => {
  // check if user is logged in, by checking cookie
  let username = req.cookies.username;

  // render the home page
  return res.render("home", {
    username,
  });
});

app.get("/login", (req, res) => {
  // check if there is a msg query
  let bad_auth = req.query.msg ? true : false;

  // if there exists, send the error.
  if (bad_auth) {
    return res.render("login", {
      error: "Invalid username or password",
    });
  } else {
    // else just render the login
    return res.render("login");
  }
});

app.get("/welcome", (req, res) => {
  // get the username
  let username = req.cookies.username;

  // render welcome page
  return res.render("welcome", {
    username,
  });
});

app.post("/process_login", (req, res) => {
  // get the data
  let { username, password } = req.body;

  // fake test data
  let userdetails = {
    username: "Bob",
    password: "123456",
  };

  // basic check
  if (
    username === userdetails["username"] &&
    password === userdetails["password"]
  ) {
    // saving the data to the cookies
    res.cookie("username", username);
    // redirect
    return res.redirect("/welcome");
  } else {
    // redirect with a fail msg
    return res.redirect("/login?msg=fail");
  }
});

app.get("/logout", (req, res) => {
  // clear the cookie
  res.clearCookie("username");
  // redirect to login
  return res.redirect("/login");
});

app.listen(PORT, () => console.log(`server started on port: ${PORT}`));

第4步 - 设置EJS视图

服务器将呈现以下EJS视图。

  1. 主页--当用户向端点http://localhost:4000/ ,用户将访问的第一个页面。
<!-- src/views/home.ejs -->

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Home</title>
        <link rel="stylesheet" href="/styles.css">
    </head>
    <body>
        <div class="welcome">
            <h4>
                Hello there, welcome.
            </h4>
            <%if(locals.username){%>
            <p>You are logged in as <%=username%></p>
            <a href="/logout">Logout</a>
            <%} else { %>
            <a href="/login">Login</a>
            <% } %>
        </div>
    </body>
</html>
  1. 一个登录页面--一旦用户在主页上选择Login ,他们将被重定向到端点http://localhost:4000/login 的登录页面。
<!-- src/views/login.ejs -->

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Login</title>
        <link rel="stylesheet" href="/styles.css">
    </head>
    <body>
        <div class="form">
            <form action="/process_login" method="post">
                <h2>Login</h2>
                <% if(locals.error) {%>
                <p class="text-danger"><%=error%></p>
                <%}%>                
                <div class="input-field">
                    <input type="text" name="username" id="username" placeholder="Enter Username">
                </div>
                <div class="input-field">
                    <input type="password" name="password" id="password" placeholder="Enter Password">
                </div>
                <input type="submit" value="LogIn">
            </form>
        </div>
    </body>
</html>
  1. 一个欢迎页面--如果用户成功登录,他/她将被重定向到端点http://localhost:4000/welcomewelcome 网页。在这里,用户可以注销并被重定向到Home page
<!-- src/views/welcome.ejs -->

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Welcome</title>
        <link rel="stylesheet" href="/styles.css">
    </head>
    <body>
        <div class="welcome">
            <h4>
                Hi <%=username%>, you are welcome here
            </h4>
            <a href="/logout">Logout</a>
        </div>
    </body>
</html>

接下来,添加一些CSS来为上述视图设置样式。

/* src/public/styles.css */

body {
  display: flex;
  justify-content: center;
}

form {
  display: flex;
  flex-direction: column;
}

.text-danger {
  color: red;
}

.input-field {
  position: relative;
  margin-top: 2rem;
}

.input-field input {
  padding: 0.8rem;
}

form .input-field:first-child {
  margin-bottom: 1.5rem;
}

form input[type="submit"] {
  background: linear-gradient(to left, #4776e6, #8e54e9);
  color: white;
  border-radius: 4px;
  margin-top: 2rem;
  padding: 0.4rem;
}

第5步 - 运行应用程序

要启动服务器,运行npm run dev

A login server running

第6步 - 测试应用程序

现在让我们测试一下该应用程序,看看是否一切正常。

  • 在浏览器中打开http://localhost:4000/ ,你将得到这个home page

A Home page

  • 点击home page 上的login 链接,将重定向到这个login page

A login page

  • 然后我们需要输入usernamepassword 。由于这是一个简单的演示,我们没有使用数据库来保存登录凭证。我们已经在index.js 文件中定义了它们。
  // fake test data
  let userdetails = {
    username: "Bob",
    password: "123456",
  };

使用Bob 作为用户名,使用123456 作为密码。如果凭证正确,你将得到这个欢迎页面。

A welcome page

我们在这里使用cookie,所以当Bob 成功登录时,Bob的username 的cookie将被保存在他的浏览器中。进入你的浏览器检查工具🡆应用程序🡆cookies 🡆选择你的域名http://localhost:4000/

Bob login cookie

现在这个cookie已经被保存了,当Bob再次访问该网站时,他将不必再提供登录凭证。继续尝试,当你访问主页路线时,这是否有效(http://localhost:4000/)。

当鲍勃选择log out ,服务器将发送一个响应,清除浏览器中保存的login cookie。

试着注销,看看会发生什么。

Logout clear cookies

保存的cookie将被清除,因此,当Bob想访问这个域名的页面时,他需要再次登录。

Cookie有什么用处?

作为一名开发者,你可能会想知道cookie有什么用,为什么开发者需要使用它们。这里有几个用例,解释了收集和保存用户偏好的重要性,以及cookie中的其他有用信息。

会话cookie

它跟踪用户在网站内互动过的会话。服务器需要记住的关于你的任何一种信息都包括在会话管理中。例如,在购物网站中,每个cookie对特定用户来说都是独一无二的。正如所讨论的,电子商务网站可以使用cookie来存储你的购物车的内容。

在网络游戏中,cookies可能被用来存储你的分数。

个人化

这保存了用户的偏好,如主题、语言和不同的网站设置。换句话说,cookies可用于个性化,确保用户获得他们正在寻找的体验。

跟踪用户活动

Cookies可以跟踪用户在网站上的任何行为。这包括你访问的页面,你点击的链接,以及你与一个网页互动的时间。这种跟踪大多是由第三方进行的,以分析一个网站。由于cookie是特定于一个用户的,它可以被用来确定有多少独特的用户访问一个网站。

结论

请注意,cookie不是用来传输敏感数据的。作为一个开发者,你必须确保你发送给客户的响应不包含敏感信息,如密码。Cookie保存在浏览器上,因此,如果它落入坏人手中,可以被操纵。

有一些cookie规定,确保cookie不被错误地使用。这些准则还限制了cookie可以从用户那里获得的数据类型,以避免损害用户隐私。

根据GDPR,互联网标识符(如cookie)被认为是个人数据,组织只能在客户的许可下利用它们。这就是为什么当你访问一个网站时,cookie选择提示如此常见的原因。

我希望本指南能帮助你了解什么是cookie,以及如何在Node.js网络应用程序中以正确的方式使用它们。编码愉快。