浏览器网络与安全
总的来说,这一块的知识,互相之间其实是紧密相连在一起的,比如,我们说浏览器安全,就会谈到跨域,谈到 cookie,但是他们其实是 http 标准制定的,是网络协议的一部分。总的来说,将牵扯到这些东西:
- http 协议在浏览器中的实现,基于这种实现的跨域
- 浏览器存储,即存放临时文件的方式
- 基于上面两者,所引发的安全问题,如 xxs, csrf
跨域
过去的页面并没有相互之间的通信能力,更没有和服务器端通信的能力,也就不存在跨域问题。但是随着 js 的权利越来越大,这两种能力被赋予给 js,跨域问题产生了。
浏览器的行为和功能在由 W3C(World Wide Web Consortium)和 WHATWG(Web Hypertext Application Technology Working Group)等组织的规范所指导。他们当然也提供了 http 在浏览器中应当如何实现,并制定了针对跨域 js 请求的相关规范。
具体参考“跨域问题”一节。
cookie 和 session
cookie 在规范的 http 实现标准的一部分中被制定。Cookie 规范最初的制定目的是因为,HTTP 是一种无状态协议,这意味着每个HTTP请求都是独立的,服务器无法记住前一次请求的信息。为了方便的标识多个目标实际上是同一个客户,cookie 被制定。
而 session 同样也是作为状态信息的目的,但它存储在服务器端,它不是标准,只是服务器端存储临时的一条数据,习惯叫做"session"。cookie 主要的目的是为了唯一标识客户端,它保存在浏览器端,而 session 可以配合 cookie,来存放本次会话的临时数据,例如本次用户登录信息、购物车的内容等。
总的来说,Cookie用于在用户端存储唯一标识的信息,而Session在服务器端存储更敏感和持久的数据,Cookie 可以存储 Session_id。
cookie 的使用
Cookie 是直接存储在浏览器中的一小串数据。通过服务器使用响应头 Set-Cookie 设置,编码后最大容量为 4KB。设置了 cookie 之后,以后浏览器发送到该服务器(域名)的所有请求都将携带该 cookie,直到它过期(Expire/max-age)。
浏览器中 cookie 的存储是基于域名划分的,本域下的脚本只能访问到相同域存储的 cookie。document.cookie 保存了本域的 cookie。
Cookie 有几个选项,其中很多都很重要,应该设置:
- domain:
cookie只有在设置的域(domain)下才能被访问到。 - path:指定了哪些子路径允许访问这个
cookie。 - httpOnly标识:通过拥有该标识,浏览器将禁止所有的 js 代码访问此 cookie。
- secure标识: 由于
cookie是基于域名划分的,所以如果我们在http://site.com上设置了 cookie,那么该 cookie 也会出现在https://site.com上,反之亦然。事实上,出于安全性考虑,cookie本来就不应该被http所使用才对。通过secure标识,会强制cookie只在https下才能访问。 - expire/max-age:过期时间点/时间。
- samesite=lax/strict:用于避免
csrf攻击。
cookie 的处理
这部分主要涉及到正则表达式。
function getCookie(name) {
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
function setCookie(name, value, options = {}) {
options = {
path: '/',
// add other defaults here if necessary
...options
};
if (options.expires instanceof Date) {
options.expires = options.expires.toUTCString();
}
let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value);
for (let optionKey in options) {
updatedCookie += "; " + optionKey;
let optionValue = options[optionKey];
if (optionValue !== true) {
updatedCookie += "=" + optionValue;
}
}
document.cookie = updatedCookie;
}
function deleteCookie(name) {
setCookie(name, "", {
'max-age': -1
})
}第三方 cookie
这是一个有趣的东西:
当我们访问一个网站的时候,有时候会提示是否允许第三方 cookie。这是因为目标网站中对外部的第三方网站发送了请求,并且该网站使用了 cookie。这意味着,第三方网站也知道了你访问了目标网站,因为你的浏览器中自动的记录了这个第三方网站的 cookie...无论你再访问随便哪个网站B,C,只要他们都用到了这个第三方网站的资源,就不得不发送请求给第三方网站,并且携带这个 cookie...这个第三方网站得到了相同的cookie,自然就知道了你访问了网站 B、C...
这将暴露浏览行踪。
也就是说,cookie 的使用和它的本意有点儿大相庭径...
CSRF(Cross-Site Request Forgery)
cookie 会在每次请求相应域的目标时被发送,这就是它信息泄露的关键点。无法保证每次请求,都是相同域的脚本所请求的。请求的发送方式除了 js,还有 html。html 本身不属于跨域请求,不会被拦截。
跨网站请求伪造(CSRF),由于浏览器拥有包含登录信息的 cookie,恶意网站就可以向该网站的服务器发送请求的表单,该表单由于不是跨域 js 请求所以不会被浏览器拦截,而浏览器还会自动添加目标域的 cookie ,从而导致伪造用户进行非法操作。
解决方法:给 cookie 添加 secure、samesite、httpOnly 字段。
TOKEN、JWT
严格地讲,cookie 的初衷并不是用于身份认证,而是用于客户端的标识手段。想将它用于存放敏感信息,需要通过设置很多安全相关字段来实现。相对于 cookie,一段 token 是更好的选择,用于专门验证用户身份。
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户进行存储。这段 json 数据有三个部分:
- Header(头部)
- Payload(负载)
- Signature(签名)
Header 部分是一个 JSON 对象,描述一些元数据和必要信息;负载则是实际的存储内容,而签名则是服务器端加密后的一段数据,用于防止数据在传输过程中被篡改。通过 jwt,可以安全的实现身份认证。
Cookies,session 和 JWT 的权衡
场景上,对于中小型网站来说,Session_id + Cookies 通常就能满足需求。
对于高访问量的网站,如果使用 session,造成了一些问题:
用户的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
cookie只是一种标识,并不适用于安全的身份验证。而且它受到跨域限制,无法方便的在客户端进行使用。必须使用
session数据持久化,写入数据库或别的持久层。各种服务收到请求后,向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。
所以更建议使用 JWT。它无状态、能够存储在客户端的 localStorage 中被获取。