在研究后端时,我遇到了这样一个误解的问题:有一个站点,一个Nodejs服务器(原则上,什么都可以,但我对Nodejs感兴趣)和一个访问者来到该站点。网站上是否有注册并不重要 - 究竟是什么识别了访问者?
我知道它是会话驱动的。我在这里读到了,但我不明白服务器究竟在哪里、为什么以及从什么数据中了解到现在它是同一个访问者?假设有两个访问者访问该站点:一个来自莫斯科,另一个来自 Vorkuta,并且该站点上有一个聊天。聊天简单而平庸 - 您无需注册即可发送帖子或聊天。有必要进行聊天,以使来自 Vorkuta 的用户消息为蓝色背景,来自莫斯科的消息为绿色背景。立即,一个新的访问者来到了这个站点,同样来自莫斯科,来自同一个 IP 地址,但是来自我们莫斯科人隔壁房间的另一台计算机(假设这是他的妻子),她(妻子的)消息应该有一个橙色背景...
因此,我不要求任何代码(除非需要一个好的示例)。请解释服务器感知访问者的逻辑。如何确定谁是谁?
我将列出我知道的所有用户识别方法。
IP地址
我指出这种方法是因为它是唯一不能伪造的方法。它可以从其他人那里借用(代理、VPN、Tor,只是一个动态 IP),但这通常比清除 cookie 更困难。删除 IP 地址是不可能的,类似于清理 cookie:肯定会有一些。由于它的相对可靠性(不是每个人都懒得让数百个代理服务器准备好更改 IP),它通常用于增强安全性:例如,它们限制来自一个 IP 的每秒/分钟/小时的最大请求数. 但是,通过同一个互联网坐着的不同人不会被IP区分,这与问题的条件相矛盾,所以我们继续前进。
普通的用户名和密码
本质很简单:我们愚蠢地在每个请求中发送登录名和密码。实现此方法的选项之一已经存在于 HTTP 协议本身中,通过 header
Authorization
已经在所有主要的 Web 浏览器和 Web 服务器中实现。在HTTP版本中,实质如下:
在第一次访问该站点时,客户端什么都没有,也不会向服务器发送任何附加信息。服务器以错误响应并添加包含登录方法信息
401 Unauthorized
的 HTTP 标头(对于简单的登录密码,这是)WWW-Authenticate
Basic realm="default"
客户端收到所有这些并要求用户输入用户名和密码。然后它再次发送请求,但带有 HTTP 标头
Authorization
,其中包含 base64: 中的用户名和密码Basic YWRtaW46MTIzNDU2
。如果这个例子被解码,我们得到admin:123456
- 登录名和密码,用冒号分隔该站点检查所有内容并正常回答,或者再次 401 并请求新的登录密码
Authorization: Basic YWRtaW46MTIzNDU2
每次在所有后续请求中都会发送此头盔。优点:
问题:
没有HTTPS,根本就没有安全性:登录名和密码,其实就是明文上网(base64不是加密)。客户端也被强制记住明文密码,服务器也知道密码(有一些认证方案,服务器可能不知道密码,但这不是问题);
浏览器中的 HTTP Basic Auth 仅适用于当前会话;重新启动浏览器后,必须再次输入登录密码。
公平地说,我注意到 HTTP 不仅可以使用简单的登录密码(也许是授权方法的完整列表),但我不会详述其他方法,因为它们的普及率很低。
随机字符串
最简单、最平衡的“安全/便利”和最流行的识别方法。世界上(可能)最常见的 PHPSESSID cookie 就是这样。要点是这样的:
当客户第一次访问该网站时,什么都没有。站点注意到这一点,创建一个新的随机字符串(更真实,因此难以拾取;至少 30 个字符),并与对请求的通常响应一起,以一种或另一种方式发送这个生成的字符串(设置-Cookie,重定向到一个特殊的链接,或者只是在响应正文中,如果它是例如 JSON API)
客户端接收到这个字符串以及响应并在某个地方记住它(浏览器本身将它存储在 cookie 中,SPA 可以将它放在 localStorage 等中)
在随后访问该站点时,客户端将此字符串添加到其请求中(cookie、身份验证 HTTP 标头或请求地址中的 GET 参数 - 在一些旧的 PHP 论坛上,您仍然可以在地址中看到会话 ID酒吧)
如果您需要更具体地识别客户端(例如通过登录密码登录),该站点然后在其数据库中记录某某随机字符串对应某某登录,然后从数据库中读取此信息在随后的请求中。
如果我们谈论 PHP,那么所有这些都内置在其中:当调用函数时
session_start()
,会从随机字母和数字创建一个 cookiePHPSESSID
(或者如果现有的已经存在,则读取它)。与此 cookie 关联的数据可通过数组获得$_SESSION
(默认情况下物理存储在服务器上的特殊目录中),您可以读取和修改它。在用户的后续请求中,会话的内容将在调用时自动从文件中读取session_start()
,并且您在处理先前请求时放入数组中的所有数据都$_SESSION
将再次可用。文档中的详细信息。优点:
简单——比较字符串是微不足道的;
更改IP地址时(这在手机上很常见),识别不会崩溃;
“在所有设备上注销”按钮的实现归结为简单地删除数据库中的所有条目,如果您为每个设备创建单独的行,您可以有选择地注销设备(一些网站提供这样的机会,例如例如,VK)。
问题:
随机字符串生成器必须是真正随机的(或者不是完全随机的,但是是抗加密的,not
uniqid()
),因为攻击者可以尝试获取伪随机性(例如,在PHP或Python中选取生成器的状态,或者选取通过 uniqid () 创建的会话,在 Invision Power board 中)。在任何情况下,您都不应该使用登录哈希、密码哈希、当前时间、单个预先准备好的字符串和其他非随机的东西作为字符串,因为这大大简化了选择。如何获得真正的随机性,请阅读您的编程语言的文档。session_start()
或者只是使用像PHP那样的现成实现;服务器上的额外负载。要找出隐藏在随机字符串后面的用户,他必须访问数据库。对于绝大多数网站来说不是问题,但对于像谷歌这样的巨头来说已经是问题了;
cookie 有时是错误的:例如,IE11会在子域中添加 cookie,即使没有被请求(已在 Edge 中修复),这可能会将数据泄露给第三方 CDN。因此,请注意您正在调整网站的浏览器如何操作 cookie。好吧,不要忘记 HttpOnly,这样您就不能通过 XSS 窃取 cookie(如果站点使用 HTTPS,还要注意 Secure)。
非随机但安全的字符串(例如 JWT)
底线是这样的:我们公然违反了上述对非随机数据的禁令,并塞进了字符串,例如,用户 ID 和可选的可用访问权限(例如,他是管理员)、到期日期字符串和其他一些数据。但!除了这一行,我们添加了一些哈希,它是根据数据加上一些只有站点知道并且不给任何人的秘密字符串计算的。当客户端请求时,站点会相应地检查哈希是否正确。这可以防止贿赂和伪造:为了伪造数据,您需要重新计算哈希值,而不知道秘密字符串的攻击者将无法做到这一点。(秘密字符串必须很长,一百个字符,这样根本就不会被拾取,因为它具有所有的安全性。)(在 JWT 中,您还可以使用 RSA 进行签名,而不仅仅是一个秘密字符串,这增加了安全,
优点:
服务器负载更小。客户端自己已经发送了所有必要的数据,服务器只需要根据这些数据和秘密字符串计算散列,并检查它是否与发送的匹配。无需去数据库:秘密字符串通常位于附近的某个变量中,所以这一切都很快完成;
与集中式数据库的独立性使得检查独立且不相关的微服务的身份验证变得容易,包括那些地理上分散在世界各地的微服务,因为它们只需要知道很少更改的秘密字符串即可检查用户发送的哈希值,并且不需要与其他微服务或与基础通信;
客户端自己可以读取 JWT 并了解他是谁(如果数据仅使用哈希保护,并且未加密);
更改IP地址时,它也不会崩溃。
问题:
实施变得更加困难。如果你自己做所有事情,你可能会搞砸并得到一个安全漏洞,所以最好采取像同一个JWT这样的现成实现(但是有时也会在其中发现漏洞,所以一定要监视新闻并阅读Habr );
“在所有设备上注销我”按钮根本无法完成。为了使带有数据的用户字符串变为无效,您必须更改秘密字符串,或者在数据库中的某个位置记住带有某某数据的某某字符串已失效。但这都是很成问题的,并且抵消了这种识别方法的所有优点。因此,这些字符串通常是短暂的:例如,Google 在其 API 中发布了一个 JWT,有效期仅为半小时(有关到期日期的信息直接存储在 JWT 中,并且还受哈希,你不需要去数据库)。
信息可能会丢失。例如,如果您在 JWT 中写入用户是管理员,然后取消管理员权限,那么站点将根据 JWT 数据继续将客户端视为管理员,直到 JWT 本身完全烂掉. 您可以从数据库中获取信息,但是使用随机字符串再次变得更容易。
JWT 和类似物通常很长,因为它们包含所有必要的信息;例如,对于大量数据,该字符串可能不适合 cookie。但是,如果您只存储用户 ID,那么这不是问题。
Supercookies 和其他指纹识别
底线是将技术用于其他目的。每个浏览器和每个操作系统都有自己的行为特性,这些特性可以用来非常准确地识别到底是谁进来了。例如,它们绘制文本略有不同,浏览器可以通过文本像素的微小差异来区分,或者不同的计算机将浏览器窗口扩展到全屏的大小略有不同(这就是为什么 Tor 浏览器不建议扩展它和阻止网站访问画布)。我不会详细描绘所有内容,我会留下链接以供进一步阅读:
Evercookies 是最持久的 cookie
Panopticlick 2.0 用于浏览器指纹识别
即使在私人模式下,基于 HSTS 的 Supercookies 也会跟踪您
优点:
问题:
准确率不是 100%。所有的 iPhone 几乎都是一样的,你不太可能区分一部 iPhone X 和另一部 iPhone X(虽然这仅适用于指纹识别,但对于超级厨师来说更容易);
用户会找到你并痛打你。
除了已经说过的关于 cookie 的缺点之外,我们还可以这样说:
如果使用 cookie(无论是 Varnish 还是nginx 中的透明缓存),透明的服务器端查询缓存将变得不可能。因此,如果您不从服务器端设置 cookie,如果不需要它们,则站点的页面将能够使用服务器缓存并打开速度更快。尝试使用标头缓存请求
Set-Cookie
完全违反常识,这意味着它们也会错过缓存。如果使用 cookie,则需要注意为静态资源设置 CDN。如果您的网站在没有
www
地址的情况下打开,例如 ,test.ru
则通过在网站上设置 cookie,可以假设此 cookie 将在请求中传输到所有子域,包括,例如,cdn.test.ru
。因此,在传统上不www
使用 . 例如yastatic.net
,Yandex。最后一个问题应该由 RFC 6265 修复,但在撰写本文时,一些主要浏览器仍然不完全支持它。这些旧浏览器仍然不能简单地被忽略,因为并非在全世界范围内旧浏览器的使用率都同样低:例如,截至 2018 年 3 月,在日本,IE 用于所有请求的 16%。这是一个拥有 1.27 亿人口的国家。如果您正在做一项全球服务,那么无论哪种方式,您都必须表现得像 RFC 6265 还不存在一样。
除了 JWT,您还可以考虑其他“长”cookie ,例如来自 ASP.NET 的 ViewState,它们与常规会话随机 cookie 具有相同的缓存和 CDN 问题,只是更糟。此类和类似的 cookie 可能非常大,很容易达到 10 KB,如果它们与每个请求一起传输到您网站上的任何图像或静态文件,那么这肯定会影响网站在所有模式下的速度。如果站点速度对您很重要,Microsoft 直接建议不要使用它们。
还有什么?
您可以使用可以进入浏览器缓存的各种标头来代替 cookie。例如,如果您曾经发送了带有某个图像或文件的 ETag 标头,那么下次浏览器会再次发送此标头的值。用户看不到你这样跟踪他,也就是说,你知道这是同一个人又进来了,所以不欢迎这样的伎俩。
全部的
如果您无法识别您的用户,那么您的网站将能够运行得更快。
通常,它们被使用
Cookies
- 用户存储在浏览器中的特定数据字符串。您可以自己制作生成它们的算法,或使用本机引擎,例如PHPSESSID
(PHP
参见函数session_start()
)。您也可以在没有他们的情况下识别用户,但在这种情况下,您将需要使用您有权访问的其他参数。这首先是用户代理(用户浏览器)及其 IP 地址。如果
PHP
这些变量存储在$_SERVER
:1. $_SERVER['HTTP_USER_AGENT']
2. $_SERVER['REMOTE_ADDR']
因此,第二个选项的准确性较低,但如果用户禁用了存储,则可以使用
Cookie
。第二种情况的错误示例 - 两个用户位于 NAT 后面并使用相同的浏览器。