可扩展的 Web 应用程序开发越来越流行,并且可以使用各种工具使开发过程更简单、更快捷。然而,这些 Web 应用程序的安全性怎么强调都不为过。简化 Web 应用程序构建过程的工具或技术可能会无意中为攻击者留下漏洞以供利用。开发人员有责任寻找这些漏洞,这些漏洞可能会为攻击者在系统中使用留下后门; JSON Web Token 或 JWT 无效是此类漏洞之一。在本教程中,我们将回顾 JWT 失效的漏洞,并构建一个使修复漏洞的令牌失效的系统。

先决条件

在本教程中,将使用 Node.js 和 Redis 缓存来构建 JWT 失效系统。你不需要知道如何使用 Redis;我将引导您完成设置,但先验知识很有帮助。完成本教程需要以下内容:

  • Node.js模块和npm的基本了解

  • JavaScript 异步/等待

  • JavaScript 尝试...捕获

JSON Web Token介绍

JSON Web 令牌,通常称为 JWT,是一种无状态令牌,用于在两方之间进行安全通信。 JWT 经常用于服务器端 Web 开发,使用 Node.js 授权客户端访问服务器或 API 端点上的资源。

npm 上的jsonwebtoken模块是 Node.js 中常用的实现 JWT 的包之一。该模块允许您在 Node.js 开发环境中对 JWT 进行签名、验证和解码。

为什么使 JWT 无效?

jsonwebtoken模块允许我们指定要签名的 JWT 的到期持续时间,在此之后令牌变得无效。这是目前使用此 npm 模块使 JWT 无效的唯一方法。此选项使攻击者能够使用不再使用但尚未过期的令牌。为了提高我们系统的安全性并防止对服务器的未经授权的访问,应实施一个使任何不再使用但到期日期尚未过去的令牌无效的系统。

为了说明这一点,考虑一个简单的系统,其中客户端必须在访问服务器上的资源之前获得授权。当客户端注销时,令牌会从客户端删除,但此令牌可能仍然有效,即尚未过期。如果这个令牌被第三方访问,它可以被用来在客户端不知情的情况下访问服务器上的资源。这是我们系统中必须解决的重大安全漏洞。

在本文中,我将向您展示一种简单的方法,该方法可用于使 JWT 无效并解决之前使用 jsonwebtoken 模块发现的安全漏洞。

使用清单系统使 JWT 无效

使 JWT 失效的一种方法是创建一个清单系统,其中包含已创建的有效和无效 JWT。开发此系统是为了在系统使用之前检查 JWT 的状态,并删除过期的,以提高系统的效率。 JWT 是无状态的这一事实意味着它们不需要保存在数据库中,因为它是自包含的并且通常缓存在客户端,因此 Redis 缓存系统非常适合该标准。

下面实现了使用清单使 JWT 无效的系统;

设置环境

  • 如果您安装了 Node 跳过此步骤,否则从此处的下载节点并将其安装在您的机器上。如果您更喜欢使用包管理器来安装,请阅读这个对于所有操作系统。

  • 要检查您是否安装了 Node.js,请运行以下命令:

节点-v
  • Node.js 与 Npm 捆绑在一起,您无需再次安装。运行以下命令以确认这一点:
npm -v
  • 在本地机器上从下载并安装 Redis。要检查 Redis 是否安装正确,请运行以下命令:
redis 服务器

上面的命令启动 Redis 服务器。在另一个终端中,运行以下命令以确认 Redis 是否正常工作:

重分发
//乒乓
  • 现在我们已经设置了 Node.js 和 Redis 缓存,让我们通过创建项目目录(根据偏好命名)或在现有目录中继续来初始化我们的项目。

  • 使用以下命令创建一个文件并将其命名为tokenCache.js(或根据偏好):

触摸令牌缓存.js
  • 使用后面的代码行初始化 Npm,如果您的目录中已经初始化,则跳过此步骤。
npm 初始化 -y

安装依赖

  • 使用下面的逗号从 npm 安装Node-Redis;这是 Node.js 的 Redis 客户端。基本上,它允许在本地机器上使用 Node.js 与 Redis 进行交互。您可以在此处查看 Redis推荐的其他客户端。
npm 我 redis
  • 使用以下命令从 npm 安装jsonwebtoken:
npm i jsonwebtoken

清单制度的实施

步骤 1

tokenCache.js文件中,将使用以下代码片段创建与本地计算机上的 Redis 缓存的连接:

const redis = require("redis");
const jwt = require("jsonwebtoken")

(async function () {
    const client = redis.createClient();
    client.on("connect", (err) => {
        console.log("Client connected to Redis...");
    });
    client.on("ready", (err) => {
        console.log("Redis ready to use");
    });
    client.on("error", (err) => {
        console.error("Redis Client", err);
    });
    client.on("end", () => {
        console.log("Redis disconnected successfully");
    });
    await client.connect();
    return client;
})()

第二步

导入文件顶部的jsonwebtoken模块,如下代码行所示:

const redis = require("redis");
const jwt = require("jsonwebtoken")

步骤 3

实例化 Redis 并建立与缓存的连接。接下来,我们创建一个将 JWT 作为参数并将其添加到清单的函数,如下面的代码块所示:

const addToken = async(token) => {
    try {
        const check = await client.EXISTS(token); // check if token exists in cache already
        if (check == 1) console.error("Token already exist in cache");
        await client.SET(token, "valid"); // set the JWT as the key and its value as valid
        const payload = await jwt.verify(token, "secret-key") // verifies and decode the jwt to get the expiration date
        await client.EXPIREAT(key, +payload.exp); // sets the token expiration date to be removed from the cache
        return;
    } catch (e) {
        console.error("Token not added to cache")
    }
};

步骤 4

上面,已经创建了一个将令牌添加到清单的函数。接下来,将创建另一个检查缓存中令牌有效性的函数,请参见下面的代码片段。

const checkToken = async(token) => {
    try {
        const status = await client.GET(token); // get the token from the cache and return its value
        return status;
    } catch (e) {
        console.error("Fetching token from cache failed")
    }
};

步骤 5

最后,将创建一个使不再使用但到期日期尚未过去的令牌无效的函数,这在下面的代码块中显示。

const blacklistToken = async(token) => {
    try {
        const status = await client.SET(token, "invalid"); // sets the value of the JWT to be invalid
        if (status == "nil") console.error("Token does not exist in cache");
        const payload = await jwt.verify(token, "secret-key") // verifies and decode the jwt to get the expiration date
        await client.EXPIREAT(token, +payload.exp); // sets the token expiration date to be removed from the cache
        return;
    } catch(e) {
        console.error("Token not invalidated")
    }
};

tokenCache.js中包含的代码块应该类似于下面的代码块:

const redis = require("redis");
const jwt = require("jsonwebtoken")

(async function () {
    const client = redis.createClient();
    client.on("connect", (err) => {
        console.log("Client connected to Redis...");
    });
    client.on("ready", (err) => {
        console.log("Redis ready to use");
    });
    client.on("error", (err) => {
        console.error("Redis Client", err);
    });
    client.on("end", () => {
        console.log("Redis disconnected successfully");
    });
    await client.connect();
    return client;
})()

const addToken = async(token) => {
    try {
        const check = await client.EXISTS(token); // check if token exists in cache already
        if (check == 1) console.error("Token already exist in cache");
        await client.SET(token, "valid"); // set the JWT as the key and its value as valid
        const payload = await jwt.verify(token, "secret-key") // verifies and decode the jwt to get the expiration date
        await client.EXPIREAT(key, +payload.exp); // sets the token expiration date to be removed from the cache
        return;
    } catch (e) {
        console.error("Token not added to cache")
    }
};

const checkToken = async(token) => {
    try {
        const status = await client.GET(token); // get the token from the cache and return its value
        return status;
    } catch (e) {
        console.error("Fetching token from cache failed")
    }
};

const blacklistToken = async(token) => {
    try {
        const status = await client.SET(token, "invalid"); // sets the value of the JWT to be invalid
        if (status == "nil") console.error("Token does not exist in cache");
        const payload = await jwt.verify(token, "secret-key") // verifies and decode the jwt to get the expiration date
        await client.EXPIREAT(token, +payload.exp); // sets the token expiration date to be removed from the cache
        return;
    } catch(e) {
        console.error("Token not invalidated")
    }
};

结论

提供的实现可能不是生产就绪的;尽管如此,它旨在表达一种使 JWT 无效的方法,并且可以由读者进行微调。

这是我个人使用的方法,除其他外,它可以扩展为使访问和刷新令牌无效。对缺陷以及如何提高这种方法的效率的意见表示赞赏。

感谢您抽出时间来阅读;为一次改变世界而欢呼一行代码。

Logo

Redis社区为您提供最前沿的新闻资讯和知识内容

更多推荐