很多人对于cookies,sessions,基于令牌的身份验证(token-based authentication)和JWT这些术语存在着一些混淆和不理解
今天,我想谈谈当人们提到“JWT vs Cookie”、“Local Storage vs Cookies”、“Session vs token-based authentication”和“Bearer token vs Cookie”时,人们究竟是想比较什么呢?
提示一下 - 我们应该停止比较JWT和Cookies!
在接下来的内容中,我将先介绍什么是XSS和CSRF攻击,以及如何使用JWT和CSRF令牌实现基于令牌的身份验证,从而防范这些攻击。
让我们开始吧!
关键术语
首先我们需要了解一些关键术语以及它们之间的差异
客户端
客户端应用程序,在本文中,我们专门讨论网络浏览器,例如Firefox,Brave,Chrome,等等
服务端
幕后默默运转的机器。。
请求/响应头(Request/Response Headers)
HTTP头信息,注意不区分大小写
Cookie
也称作HTTP cookie,web cookie,浏览器cookie,是服务器发送给客户端的一小段信息。
Cookies 存储在浏览器的 Cookies Storage中,通常用于身份验证、个性化和跟踪。
Cookie是通过 Set-Cookie 响应头,以kv对的形式设置的。浏览器接收cookie,并且将自动保存在浏览器的 Cookies Storage中(document.cookie)。
注意:设置了HttpOnly, Secure, 和 SameSite=Strict 属性的Cookies会更加安全
例如,设置了HttpOnly属性,Cookies就无法通过JavaScript获取,因此可以防止XSS攻击。
可以在MDN上阅读更多内容并查看其他属性的功能(它们都非常实用)
XSS攻击
XSS也就是所谓的“跨站脚本攻击“
在Web开发中,通过JavaScript可以在相同的域内访问Web Storage(例如Local Storage)。然而,这也使得Web存储容易受到XSS攻击。简单来说,XSS攻击是一种漏洞类型,攻击者通过在页面上注入恶意的JS代码,从而获取用户信息或进行其他攻击行为。
基本的XSS攻击可以通过表单注入JS代码,例如攻击者会将alert(localStorage.getItem(‘your-secret-token’))这段代码放入表单中,如果浏览器运行这段代码,攻击者就能成功获取存储在本地存储中的敏感数据。此外,如果攻击者在表单中注入的代码可以在其他用户的浏览器中运行,那么他们也可以看到这些数据,这是非常危险的。
如果想进一步了解XSS,请观看这个视频
CSRF攻击
CSRF就是“跨站请求伪造”攻击
Cookies容易受到CSRF攻击,由于浏览器会自动在所有请求中发送Cookies,攻击者可以利用此特性,来获取对受信任站点的认证访问。如果需要更多解释,请查看:“简单易懂的CSRF原理”
要在使用Cookies(SameSite=None)的情况下保护您的站点免受CSRF攻击,请看这个回答
另外这里也介绍了有关预防CSRF的知识,虽然比较枯燥
如果仍然不了解CSRF,请看这个“CSRF详解”视频
Cookies Storage
Cookies Storage,是一种客户端存储方式,用于存储 HTTP Cookies
需要注意的是,浏览器会在每个请求中自动发送 Cookie,无需客户端代码,通过 cookie 请求头来实现。这也正是 Cookies 存储易受 CSRF 攻击的原因所在。
Web Storage
包括Local Storage和Session Storage,是一种客户端存储机制,通常用于以键值对的形式存储数据。
Local Storage可用于永久保存数据,即使浏览器关闭或计算机重新启动也不会丢失。而Session Storage则只在当前会话中保留数据,一旦用户关闭了浏览器选项卡或浏览器窗口,存储的数据就会被删除。
尽管 Web Storage 是一种非常方便的客户端存储机制,但它们也存在一些安全风险。与 Cookies 不同,Web Storage 的数据无法在浏览器请求中自动发送。然而,由于 Web Storage 是通过 JavaScript 在客户端中访问的,因此它们容易受到 XSS 攻击的影响。攻击者可以通过注入恶意脚本来访问和篡改存储在 Web Storage 中的敏感数据。因此,不建议将私有、敏感或身份验证相关的数据存储在 Web Storage 中。
有关Web Storage的更多信息请查看MDN
Cookies (Storage) vs Web Storage
LOCAL/SESSION STORAGE | COOKIES (STORAGE) | |
---|---|---|
JavaScript | 同域下可通过 JavaScript 访问 | 当 Cookies设置了 HttpOnly 标志时,就不能通过 JavaScript 访问 |
XSS attacks | 容易受到XSS攻击 | 设置HttpOnly标志后不易收到XSS攻击 |
CSRF attacks | 不易收到CSRF攻击 | 容易受到CSRF攻击 |
缓解措施 | 不要将私有、敏感或身份验证相关的数据存储在 Web Storage | 为了防止 CSRF 攻击,开发人员应该使用 CSRF token或其他预防措施 |
JWT
全称“JSON Web Tokens”,常用于身份验证和授权
JWT是一个开放标准(RFC 7519),意味着JWT是一个通用的token格式
通常,JWT存储在Local Storage或者Cookies (storage)中
请记住,JWT不以任何方式加密。相反,它是以Base64编码的。尝试在jwt.io上解码任何JWT
为什么使用JWT
JWT一般是配合“基于令牌的身份验证(Token-based Authentication)”使用,这时使用JWT的系统将更容易水平扩展
为什么?因为验证JWT不需要服务器和数据库之间的任何通信。换句话说,身份验证可以是无状态的
请查阅Auth0的文档以获取更多信息。
停止比较JWT和Cookies
请停止比较JWT和Cookie,因为它们本身都代表完整的身份验证机制。
JWT只是一种令牌格式,而Cookie实际上是一种HTTP状态管理机制。
正如我们所说,Web Cookie可以包含JWT,并且可以存储在浏览器的Cookies存储中。
因此,我们需要停止比较JWT和Cookie。这是因为它们具有不同的用途和功能,不能简单地进行比较。JWT用于在客户端和服务器之间传递信息,而Cookie用于管理HTTP状态并存储会话信息。在实际应用中,它们可能会被同时使用,但它们的作用是不同的。
基于会话的身份验证vs基于令牌的身份验证(Session-based vs Token-based Authentication)
我们实际上应该问的问题是:基于令牌(token)身份验证和基于会话(session)身份验证之间的区别是什么?
TOKEN-BASED | SESSION-BASED |
---|---|
无状态 | 有状态 |
服务端不存储身份验证状态 | 服务端存储身份验证状态 |
易于水平扩展 | 不易于水平扩展 |
一般通过JWT做认证 | 一般通过Session ID做认证 |
通常通过HTTP请求头(例如Bearer)发送到服务器,也可以使用Cookies传递令牌 | 将Session ID作为Cookies请求头发送到服务器 |
相对于基于会话的身份验证,撤销用户令牌身份验证更加困难 | 相对于基于令牌的身份验证,撤销用户会话更加容易 |
Cookie vs Bearer Tokens
现在我们知道了Cookie的工作原理,接下来让我们了解一下“Bearer tokens”这个术语。让我们假设我们将使用JWT作为我们的身份验证令牌。
所谓的“Bearer token”是一个字符串(例如JWT),它放置在HTTP请求的Authorization头中。与浏览器Cookie不同,它不会自动存储在任何地方,从而使跨站请求伪造攻击(CSRF)成为不可能。
GET http://www.example.com
Authorization: Bearer my_bearer_token_value // HTTP Request Header
要使用“Bearer token”,我们需要明确地将JWT存储在客户端的某个位置(Cookies Storage或Local Storage),并在请求时将该JWT添加到HTTP Authorization头中。
如果你的Cookies(例如带有JWT的Cookies)设置了HttpOnly标志,那么将无法使用JavaScript获取令牌。
等等,我们可以使用本地存储(Local Storage)来存储JWT吗?
请记住,使用本地存储(Local Storage)会使我们的JWT容易受到跨站脚本攻击(XSS)的攻击。因此,你会经常听到人们强烈建议不要将JWT存储在本地存储中。
此时,Cookie似乎是我们唯一的选择。但是请记住,这使我们的网站容易受到跨站请求伪造攻击(CSRF)的攻击。
CSRF防范
Same-site Cookies
Same-site Cookies可以有效地防止CSRF攻击,但这也带来了其他问题。
假设不使用Same-site Cookies,还有以下的办法
常见的CSRF防范方法
不考虑JWT,以下是两种最常见的CSRF防范方法:
- 同步令牌模式(Synchronizer token pattern)
- Cookie-to-header-token模式
请查看这个StackOverflow答案,以获得上述两种方法的快速摘要。
很好。但现在出现了一个问题 - 我们如何使用JWT实现这一点呢?
修改后的“Cookie-to-header token”方法
老实说,我不太确定这种方法是否有一个合适的名称。我从Hubert Sablonnière的这个关于100%无状态JWT(JSON Web Token)的精彩演讲中发现了这种方法。我强烈、强烈地建议你去看一看。它值得你花一个小时去了解。
简而言之,修改后的方法(在我看来)与原始的Cookie-to-header token方法相似,但有一些调整:
- anti-CSRF令牌在单独的响应头(例如X-CSRF-Token)中返回,而不是在Set-Cookie响应头中返回
- 在Set-Cookie响应头上设置JWT
(附:Cloudflare Worker demo和GitHub的地址)
解释:
-
用户登录后,服务器将使用csrfToken作为JWT声明的一部分签署JWT(用于在步骤6中进行验证)。
{ "email": "your@email.com", "exp": 1666798498, "csrfToken": "1449bd3e-41c2-45cb-a538-73c7ad80ca2c", "iat": 1666794898 }
生成的csrfToken应该是不可预测的,并且对于每个用户会话应该是唯一的。
-
JWT将以字符串形式存入Cookie,设置在Set-Cookie响应头中。另一方面,随机生成的csrfToken将设置在X-CSRF-Token响应头中。
-
随着Set-Cookie头被设置,浏览器将自动将JWT存储在Cookies(存储)中。X-CSRF-Token头中的csrfToken将被提取并设置在浏览器的Local Storage中。
-
当触发请求(例如GET /hello)时,浏览器将从Local Storage中获取csrfToken。
-
从Cookies(存储)中获取的JWT和从Local Storage中获取的csrfToken将放入请求头,并发送到服务器。
-
服务器将验证JWT,并检查请求头中的csrfToken是否与JWT中的csrfToken声明相匹配,以验证CSRF令牌是否有效。
原作者的Demo
上一节中的时序图其实就来自原作者的demo,图后给出了github的地址
另外为了测试CSRF攻击,可以利用这个小工具
并且原作者也创建了一个名为csrf-attack-demo的分支,可以在本地运行它,以模拟对网站的CSRF攻击。
总结
实际上,只要身份验证不是自动进行的(例如使用浏览器中的Cookies),就不必担心CSRF攻击。
例如,如果应用程序通过Authorization头附加身份验证凭据,则CSRF就是不可能的,因为浏览器无法自动验证请求。
最后要注意,我们不能简单地说某种解决方案一定比另一个更好,而应该考虑每种身份验证方式的优缺点,并根据我们的需求和情况选择合适的方式。