Web 身份验证:Cookie 与 Token

Web 身份验证:Cookie 与 Token

应用开发一般都少不了身份验证,而身份验证机制的稳定性对所有应用程序都变得至关重要。具体选择何种方式进行身份验证可以根据项目及团队情况来衡量,在决定之前需要先理解WEB身份验证常见的两种方式:基于 Cookie 的身份验证和基于令牌(Token)的身份验证。

身份验证是将用户凭据交换为唯一身份标识的过程。

在基于 Cookie 的身份验证中,此唯一标识符 (Cookie) 在服务器端创建并发送给浏览器。

当登录 Web 应用程序时,浏览器将从其应用程序的服务器接收一个 Cookie,浏览器将存储它并将该 Cookie 与每个后续请求一起发送,以验证请求来自同一用户。

为了更好地理解 Cookie 的工作原理,下面将这个过程分解为 5 个部分。

1. 使用凭据登录到应用程序

cookie使用凭据登录到应用程序

2. 服务器验证凭据并在创建 session

服务器验证凭证成功后,创建 session ,可以存储在内存或数据库中,为了更好的扩展建议将其存储在数据库中。如果是存储在内存中,在使用负载均衡或多服务器部署的场景下会出现 session 问题。

服务器验证凭据并在创建 session

这个 cookie 通过名称值对发送,它包含一个唯一的 id 来标识用户。

服务器通过将cookie包含在Set-Cookie 标头中来响应浏览器

除此之外,cookie 还可以包含到期日期、作用域和有效时间等详细信息。具有多个 Set-Cookie标头的示例响应如下所示:

HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: <cookie-name>=<cookie-value>
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<number>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain>
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly

[page content]

当服务器收到带有 Cookie 的请求时,它会将 Cookie 中的 session ID 与数据库中的 session 进行比较以验证用户的有效性。

浏览器将 Cookie 存储在存储中并与后续请求一起发送

可以使用浏览器开发工具在应用程序部分下的 Cookie 存储中找到浏览器中保存的所有 Cookie。

Cookie 存储中找到浏览器

5. 当用户注销时,服务器将从数据库中删除 session

当用户注销时,服务器将从数据库中删除 session

一旦用户注销,服务器将通过清除数据库 session 并使 Cookie 过期,浏览器也会从 Cookie 存储中删除它。

上面简单介绍了一下 Cookie 的工作流程,下面来看看其特征与优缺点。

这是一个完全自动化的过程

如果使用 Cookie 进行身份验证,则无需明确开发任何内容来向请求添加 Cookie。

浏览器会负责 Cookie 的处理,它会自动为所有请求添加 Cookie。

尽管这种自动化过程使开发变得更容易,但也有一些缺点。例如,有些请求不需要任何身份验证,但是使用这种方法,Cookie 也将在每个请求中被发送。

此外,CSRF 攻击者可以利用这种机制来欺骗浏览器向虚假网站发送带有 Cookie 的请求。

安全措施

默认情况下,基于 Cookie 的身份验证对攻击没有有效的保护,它们主要容易受到跨站脚本(XSS)和跨站请求伪造(CSRF)攻击。

但是,可以显式地修改 Cookie标头来保护它们免受此类攻击。

例如,在设置 Cookie 头时使用 HttpOnly 属性可以很容易地保护 Cookie 免受 XSS 攻击。

Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly

此外,可以在 Cookie 标头中使用 SameSite 属性来有效地防止 CSRF 攻击。

Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Lax

SameSite 属性有 3 个值可用:

  • SameSite=Lex:将确保浏览器不会在跨站点请求时发送 Cookie(如果没有定义 SameSite 属性,这是 Cookie 的默认行为)。
  • SameSite=Strict : 将确保浏览器仅针对同站点请求发送 Cookie
  • SameSite=Note 将允许通过跨站点和同站点请求发送 Cookie

通常适用于单个域名

除非特别配置,否则 Cookie 仅适用于单个域名。尽管从表面上这似乎是一种限制,但它是默认情况下强制执行单一来源的最强大功能之一。但是,如果前端和后端 (API) 来自不同的域名或子域名,则需要在 Cookie 中将其明确列入白名单。否则,浏览器不会随请求一起发送 Cookie。

不适合开放API

如果正在构建一个API来向客户端公开服务,那么 Cookie 可能不是最佳的选择。除非客户端只是浏览器,否则它会使客户端变得复杂。

例如,开发的是一款手机应用,与令牌Token相比,拥有 Cookie 将使移动应用程序 Cookie 管理变得复杂。

可能存在可扩展性问题

如前所述,服务器负责 Cookie 设置,需要在数据库中为每个用户存储 session 。

尽管有成熟的方法来处理可扩展性(例如,使用像 Redis 这样的内存数据库作为 session 的存储),但它仍然增加了更多的复杂性。

但是,随着用户数量的增加,在扩展和管理这些 session 时可能会出现问题。

最适合存储额外的数据

由于这种方法为每个用户维护单独的 session,所以可以存储额外的数据到 session 中。

通过 Cookie 和 session,可以存储特定的数据,如用户个性化、访问控制和 session。然后,它允许将其用于后续请求。

然而,也可以用 Token 来实现这一点。例如,使用JWT令牌,可以存储 Claims 数据。然而,由于它将增加 Token 的大小,保留更多Token将影响更高的网络利用率。

如果我们只考虑单个请求,这可能没有意义,但当事情聚集和扩展时,开销就会变得显而易见。

由于 Cookie 提供了 HTTP-Only 选项,可以限制 JavaScript 对它的访问。此外,它将阻止任何访问Cookie与跨站点脚本攻击。

基于令牌Token的认证

引入基于令牌的身份验证是为了解决基于 Cookie 方法的不足。

与Cookie不同,基于Token的方法需要自己实现,Token保存在客户端。

当登录到web应用程序时,服务器将验证凭据并向浏览器发送加密 Token。然后浏览器将存储这个Token,并可以添加到后续请求的授权头中。

然而,基于 Token 方法的标准实现要比上面描述的流程复杂得多。例如,OpenID Connect 引入了多个身份验证流来处理不同类型的用例。

为了更好地理解 Token 的工作方式,下面将这个过程分解为4个部分,并以使用最广泛的 Token 标准 JWT 作为实例。

JSON Web Token (JWT) 是基于令牌的身份验证中最常用的开放标准。

Token的认证工作流程

1. 使用凭据登录到应用程序

token 使用凭据登录到应用程序

2. 服务器验证凭据,生成令牌并使用密钥对其进行签名,然后将其发送回浏览器

服务器验证凭据,生成令牌并使用密钥对其进行签名,然后将其发送回浏览器

通常,需要在传输时使用加密(如 SSL)来保护通道。

在服务器端,可以使用像 jsonwebtoken 这样的 NPM 库来生成这些令牌。

npm install jsonwebtoken
const jwt = require("jsonwebtoken");
const privateKey =
    "eyJkYXRhIjp7InVzZXJuYW1lIjoiZGV2cG9pbnQifSwiaWF0IjoxNjI3Njk3ODQ2fQ";
const token = jwt.sign(
    {
        data: {
            username: "devpoint",
        },
    },
    privateKey,
    { algorithm: "HS256" },
    { expiresIn: Math.floor(Date.now() / 1000) + 60 * 60 }
);

使用 jsonwebtoken 生成的 Token 如下所示:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InVzZXJuYW1lIjoiZGV2cG9pbnQifSwiaWF0IjoxNjI3Njk3OTA2fQ.9v0S-74SH5UQwTAqgvNL43fAxQqW3_cajoDsum3TZEo
3. 将 Token 存储在浏览器存储中,并使用JavaScript添加到后续请求中

将 Token 存储在浏览器存储中,并使用JavaScript添加到后续请求中

浏览器可以将此 Token 存储在本地存储、Session storage 或Cookie 中。然后这个 Token 将被添加到必要请求的授权头中,并发送到服务器端进行请求验证。因此,需要使用 JavaScript 来实现向标头添加Token。

Authorization: Bearer <token>

此外,可以使用 jsonwebtoken 库中的 jwt.decode() 函数来解码此 Token 并在应用程序中使用有效负载数据。

4. 当用户注销时,需要手动从其存储中删除Token

一旦用户退出系统,需要手动清除存储在存储中的Token,使其无法用于进一步的请求。

Token的认证特征及优缺点

一种无状态机制

与 Cookie 不同,基于令牌的方法是无状态的。这意味着它不会在数据库或服务器中保存有关用户的任何信息。

服务器只负责创建、验证令牌,这允许构建比基于 Cookie 的方法更具可扩展性的解决方案。

安全问题

尽管令牌试图解决 Cookie 中的安全问题,但它也并不完全安全。

如果应用程序允许将外部 JavaScript 嵌入到应用程序中,则保存在浏览器中的令牌可能容易受到 XSS 攻击。

此外,由于令牌是无状态的,如果暴露在外面,在它到期之前没有办法撤销它。因此,尽可能少地保留令牌至关重要。大部分身份验证服务将 JWT 令牌的有效期设置在 5 分钟以内。

总结

基于令牌和基于 Cookie 的方法是 Web 应用程序最常用的两种身份验证机制。在本文中,讨论了它们的工作原理、特性、优缺点。

正如所看到的,这些方法都不是 100% 完美的,它们各有优缺点。因此,在选择身份验证方法时,建议根据项目要求选择一种,而不是追求完美的方法。