Skip to content

浏览器网络与安全

总的来说,这一块的知识,互相之间其实是紧密相连在一起的,比如,我们说浏览器安全,就会谈到跨域,谈到 cookie,但是他们其实是 http 标准制定的,是网络协议的一部分。总的来说,将牵扯到这些东西:

  • http 协议在浏览器中的实现,基于这种实现的跨域
  • 浏览器存储,即存放临时文件的方式
  • 基于上面两者,所引发的安全问题,如 xxs, csrf

跨域

过去的页面并没有相互之间的通信能力,更没有和服务器端通信的能力,也就不存在跨域问题。但是随着 js 的权利越来越大,这两种能力被赋予给 js,跨域问题产生了。

浏览器的行为和功能在由 W3C(World Wide Web Consortium)和 WHATWG(Web Hypertext Application Technology Working Group)等组织的规范所指导。他们当然也提供了 http 在浏览器中应当如何实现,并制定了针对跨域 js 请求的相关规范。

具体参考“跨域问题”一节。

cookie 在规范的 http 实现标准的一部分中被制定。Cookie 规范最初的制定目的是因为,HTTP 是一种无状态协议,这意味着每个HTTP请求都是独立的,服务器无法记住前一次请求的信息。为了方便的标识多个目标实际上是同一个客户,cookie 被制定。

session 同样也是作为状态信息的目的,但它存储在服务器端,它不是标准,只是服务器端存储临时的一条数据,习惯叫做"session"。cookie 主要的目的是为了唯一标识客户端,它保存在浏览器端,而 session 可以配合 cookie,来存放本次会话的临时数据,例如本次用户登录信息、购物车的内容等。

总的来说,Cookie用于在用户端存储唯一标识的信息,而Session在服务器端存储更敏感和持久的数据,Cookie 可以存储 Session_id。

Cookie 是直接存储在浏览器中的一小串数据。通过服务器使用响应头 Set-Cookie 设置,编码后最大容量为 4KB。设置了 cookie 之后,以后浏览器发送到该服务器(域名)的所有请求都将携带该 cookie,直到它过期(Expire/max-age)。

浏览器中 cookie 的存储是基于域名划分的,本域下的脚本只能访问到相同域存储的 cookiedocument.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 攻击。

这部分主要涉及到正则表达式。

js
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...无论你再访问随便哪个网站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 中被获取。

XSS攻击