如何使用 HTTPOnly Cookie 启用 CORS 以保护令牌?

已发表: 2021-12-29

在本文中,我们将了解如何使用 HTTPOnly cookie 启用 CORS(跨源资源共享)以保护我们的访问令牌。

如今,后端服务器和前端客户端部署在不同的域中。 因此,服务器必须启用 CORS 以允许客户端在浏览器上与服务器通信。

此外,服务器正在实施无状态身份验证以获得更好的可扩展性。 令牌在客户端存储和维护,但不像会话那样在服务器端存储和维护。 为了安全起见,最好将令牌存储在 HTTPOnly cookie 中。

为什么跨域请求会被阻止?

假设我们的前端应用程序部署在https://app.geekflare.comhttps://app.geekflare.com加载的脚本只能请求同源资源。

每当我们尝试向另一个域https://api.geekflare.com或另一个端口https://app.geekflare.com:3000或另一个方案http://app.geekflare.com发送跨域请求时,跨域请求将被浏览器阻止。

但是为什么浏览器阻止的相同请求使用 curl 请求从任何后端服务器发送或使用邮递员等工具发送而没有任何 CORS 问题。 它实际上是为了安全保护用户免受CSRF(跨站点请求伪造)之类的攻击。

让我们举个例子,假设任何用户在他们的浏览器中登录了他们自己的 PayPal 账户。 如果我们可以从加载在另一个域malicious.com .com 上的脚本向paypal.com发送跨域请求,而没有任何 CORS 错误/阻塞,就像我们发送同源请求一样。

攻击者可以通过将其转换为短 URL 以隐藏实际 URL 来轻松发送他们的恶意页面https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account 。 当用户点击一个恶意链接,该脚本域中加载malicious.com将发送跨域请求到PayPal用户量转移到攻击者的PayPal帐户将得到执行。 所有登录到其 PayPal 帐户并单击此恶意链接的用户都将损失金钱。 任何人都可以在 PayPal 帐户用户不知情的情况下轻松窃取资金。

由于上述原因,浏览器会阻止所有跨域请求。

什么是CORS(跨域资源共享)?

CORS 是一种基于标头的安全机制,服务器使用它来告诉浏览器从受信任的域发送跨域请求。
启用了 CORS 标头的服务器用于避免浏览器阻止跨域请求。

CORS 是如何工作的?

因为服务器已经在其 CORS 配置中定义了其受信任的域。 当我们向服务器发送请求时,响应将在其标头中告诉浏览器所请求的域是否可信。

有两种类型的 CORS 请求:

  • 简单的请求
  • 预检请求

简单请求:

CORS-simple request flow 告诉它发送一个跨域请求,但是当它收到响应时。它检查标题。

  • 浏览器将请求发送到具有origin(https://app.geekflare.com) 的跨域域
  • 服务器用允许的方法允许的来源发回相应的响应
  • 浏览器收到请求后,会检查发送的origin header值( https://app.geekflare.com )和收到的access-control-allow-origin value( https://app.geekflare.com )是否相同或通配符(*)。 否则,它会抛出 CORS 错误。

预检请求:

CORS-Preflight Request Image which show the flow of cross-origin request with OPTIONS preflight request before sending actual request for verifying headers.

  • 取决于来自跨域请求的自定义请求参数,如方法(PUT、DELETE)或自定义标头或不同的内容类型等。浏览器将决定发送预检 OPTIONS 请求以检查实际请求是否可以安全发送或不。
  • 浏览器收到响应(状态码:204,无内容)后,会检查实际请求的access-control-allow参数。 如果服务器允许请求参数。 实际发送和接收的跨域请求

如果access-control-allow-origin: * ,则所有来源都允许响应。 但除非你需要它,否则它并不安全。

如何启用 CORS?

要为任何域启用 CORS,请启用 CORS 标头以允许来源、方法、自定义标头、凭据等。

浏览器从服务器读取 CORS 头,只有在验证请求参数后才允许来自客户端的实际请求。

  • Access-Control-Allow-Origin:指定确切的域(https ://app.geekflate.com、https: //lab.geekflare.com)或通配符(*)
  • Access-Control-Allow-Methods:允许只有我们需要的 HTTP 方法(GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS)。
  • Access-Control-Allow-Headers:只允许特定的 Headers(Authorization, csrf-token)
  • Access-Control-Allow-Credentials:布尔值,用于允许跨源凭证(cookies、授权标头)。
  • Access-Control-Max-Age:告诉浏览器缓存预检响应一段时间。
  • Access-Control-Expose-Headers:指定可由客户端脚本访问的标头。

要在 apache 和 Nginx 网络服务器中启用 CORS,请按照本教程进行操作。

在 ExpressJS 中启用 CORS

让我们举一个没有 CORS 的 ExpressJS 应用程序示例:

 const express = require('express'); const app = express() app.get('/users', function (req, res, next) { res.json({msg: 'user get'}) }); app.post('/users', function (req, res, next) { res.json({msg: 'user create'}) }); app.put('/users', function (req, res, next) { res.json({msg: 'User update'}) }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') })

在上面的示例中,我们为 POST、PUT、GET 方法启用了用户 API 端点,但未启用 DELETE 方法。

为了在 ExpressJS 应用程序中轻松启用 CORS,您可以安装cors

 npm install cors

访问控制允许来源

为所有域启用 CORS

 app.use(cors({ origin: '*' }));

为单个域启用 CORS

 app.use(cors({ origin: 'https://app.geekflare.com' }));

如果您想允许源https://app.geekflare.comhttps://lab.geekflare.com 的CORS

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ] }));

访问控制允许方法

要为所有方法启用 CORS,请在 ExpressJS 的 CORS 模块中省略此选项。 但是为了启用特定方法(GET、POST、PUT)。

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'] }));

访问控制允许标题

用于允许除默认值之外的标头与实际请求一起发送。

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'] }));

访问控制允许凭据

如果您不想告诉浏览器在请求时允许凭据(即使withCredentials设置为 true ),请忽略此项

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true }));

访问控制最大年龄

提示浏览器将预检响应信息缓存在缓存中指定秒。 如果您不想缓存响应,请忽略此项。

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600 }));

缓存的预检响应将在浏览器中可用 10 分钟。

访问控制公开标题

app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['Content-Range', 'X-Content-Range'] }));

如果我们将通配符(*) 放在exposedHeaders 中,它就不会暴露Authorization 标头。 所以我们必须像下面这样显式地公开

app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization', ] }));

以上也将公开所有标头和授权标头。

什么是 HTTP cookie?

cookie 是服务器将发送到客户端浏览器的一小段数据。 在以后的请求中,浏览器将在每个请求中发送与同一域相关的所有 cookie。

Cookie 有它的属性,可以定义它以使 cookie 以我们需要的方式工作。

  • 名称cookie 的名称。
  • value: cookie-name对应的cookie数据
  • 域: cookie 将仅发送到定义的域
  • 路径: cookies 仅在定义的 URL 前缀路径之后发送。 假设我们已经定义了我们的 cookie 路径,如 path='admin/'。 Cookie 不是针对 URL https://geekflare.com/expire/ 发送的,而是使用 URL 前缀 https://geekflare.com/admin/ 发送的
  • Max-Age/Expires(number in second): cookie 何时到期。 cookie 的生命周期使 cookie 在指定时间后失效。
  • HTTPOnly(Boolean):后端服务器可以访问该 HTTPOnly cookie,但当为 true 时不能访问客户端脚本。
  • 安全(布尔): Cookie 仅在为真时通过 SSL/TLS 域发送。
  • sameSite(string [Strict, Lax, None]):用于启用/限制跨站点请求发送的 cookie。 要了解有关 cookie sameSite更多详细信息,请参阅 MDN。 它接受三个选项 Strict、Lax、None。 对于 cookie 配置 sameSite=None,cookie 安全值设置为 true。

为什么 HTTPOnly cookie 用于令牌?

将服务器发送的访问令牌存储在客户端存储中,如本地存储索引数据库cookie (HTTPOnly 未设置为 true)更容易受到 XSS 攻击。 假设您的任何一个页面是否容易受到 XSS 攻击。 攻击者可能会滥用浏览器中存储的用户令牌。

HTTPOnly cookie 仅由服务器/后端设置/获取,而不是在客户端。

客户端脚本被限制访问该 HTTPonly cookie。 因此 HTTPOnly cookie 不易受到 XSS 攻击,并且更安全。 因为它只能由服务器访问。

在启用 CORS 的后端启用 HTTPOnly cookie

在 CORS 中启用 Cookie 需要在应用程序/服务器中进行以下配置。

  • 将 Access-Control-Allow-Credentials 标头设置为 true。
  • Access-Control-Allow-Origin 和 Access-Control-Allow-Headers 不应是通配符 (*)。
  • Cookie sameSite 属性应为 None。
  • 要将 sameSite 值启用为 none,请将安全值设置为 true:使用 SSL/TLS 证书启用后端以在域名中工作。

让我们看看在检查登录凭据后在 HTTPOnly cookie 中设置访问令牌的示例代码。

 const express = require('express'); const app = express(); const cors = require('cors'); app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization' ] })); app.post('/login', function (req, res, next) { res.cookie('access_token', access_token, { expires: new Date(Date.now() + (3600 * 1000 * 24 * 180 * 1)), //second min hour days year secure: true, // set to true if your using https or samesite is none httpOnly: true, // backend only sameSite: 'none' // set to none for cross-request }); res.json({ msg: 'Login Successfully', access_token }); }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') });

您可以通过在后端语言和网络服务器中实现上述四个步骤来配置 CORS 和 HTTPOnly cookie。

您可以按照 apache 和 Nginx 的本教程按照上述步骤启用 CORS。

跨域请求的 withCredentials

Credentials(Cookie, Authorization) 默认与同源请求一起发送。 对于跨域,我们必须将 withCredentials 指定为 true。

XMLHttpRequest API:

 var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://api.geekflare.com/user', true); xhr.withCredentials = true; xhr.send(null);

获取 API:

 fetch('http://api.geekflare.com/user', { credentials: 'include' });

jQuery Ajax:

 $.ajax({ url: 'http://api.geekflare.com/user', xhrFields: { withCredentials: true } });

轴:

 axios.defaults.withCredentials = true

结论

我希望上面的文章可以帮助您了解 CORS 的工作原理,并为服务器中的跨域请求启用 CORS。 为什么在 HTTPOnly 中存储 cookie 是安全的,以及如何在客户端中使用 withCredentials 进行跨域请求。