删除Cookie
function deleteCookie(name, path, domain, secure) {
let date = new Date();
date.setTime(date.getTime() - 1);
document.cookie = [
encodeURIComponent(name), '=',
'; expires=' , date.toUTCString(),
path? ('; path=' + path): '',
domain? ('; domain=' + domain): '',
secure? '; secure': ''
].join('');
}
设置Cookie
function setCookie(name, value, expiresTimestamp, path, domain, secure) {
const date = new Date(expiresTimestamp);
document.cookie = [
encodeURIComponent(name), '=', encodeURIComponent(value),
'; expires=' , date.toUTCString(),
path? ('; path=' + path): '',
domain? ('; domain=' + domain): '',
secure? '; secure': ''
].join('');
}
获取Cookie
function getCookie(key) {
let result = key ? undefined : {},
cookies = document.cookie ? document.cookie.split('; ') : [],
i = 0,
l = cookies.length;
for (; i < l; i++) {
let parts = cookies[i].split('='),
name = decodeURIComponent(parts.shift()),
cookie = parts.join('=');
if(cookie.indexOf('"') === 0) {
cookie = cookie.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
cookie = decodeURIComponent(cookie.replace(/\+/g, ' '));
} catch(e){}
if (key === name) {
result = cookie;
break;
}
if (!key && (cookie !== undefined)) {
result[name] = cookie;
}
}
return result;
}
Cookie是什么
Cookie其实就是Web服务保存在浏览器端的一小段标记,这段标记可以跟随Http数据一起发送到服务端。
Javascript操作Cookie的API
Javascript设置Cookie
假设URL为www.itthink.tech/login
的服务想在浏览器端种下登录token
和用户唯一标识uid
,它们的值分别是abc789659hjr735
hr6gac25gfs6dw56
,对这两个Cookie有这样的要求:
- 分别在浏览器中保存一个月和半年
- 所有itthink.tech的子域名都可以访问这两个Cookie
- 所有路径都可以访问这两个Cookie
- 仅在Https协议下才可发送到服务端。
- 不可作为第三方Cookie发送到服务端
我们借此例来展示设置Cookie的API:
const oneMonth = new Date(Date.now() + (1000 * 60 * 60 * 24 * 30));
const oneMonthUTCString = oneMonth.toUTCString();
const sixMonth = new Date(Date.now() + (1000 * 60 * 60 * 24 * 30 * 6));
const sixMonthUTCString = oneMonth.toUTCString();
// 设置token
document.cookie = 'token=abc789659hjr735; domain=.itthink.tech; path=/; expires=' + oneMonthUTCString + '; secure; samesite=lax';
// 设置uid
document.cookie = 'uid=hr6gac25gfs6dw56; domain=.itthink.tech; path=/; expires=' + sixMonthUTCString + '; secure; samesite=lax';
可以看到document.cookie
被赋值两次,每一次document.cookie
只能设置一个Cookie,不能把所有Cookie一次性赋值给document.cookie
。
Javascript读取Cookie
读取document.cookie
便可以获取到当前环境下允许获取的Cookie。和设置时每次只能设置一个Cookie不同,读取时是一次性读取出所有的Cookie的名字和值,但是无法读取出每个Cookie的属性。继续上面的示例:
console.log(document.cookie);
// 打印:token=abc789659hjr735; uid=hr6gac25gfs6dw56
可以看到,当读取document.cookie
时,可以一次性读取出所有的Cookie名字和值,但是无法获取到属性。
这个是Cookie迷惑人的地方,虽然看起来cookie
是document
的属性,但是它其实是getter和setter。
Javascript操作Cookie的API就是这些了。
Http操作Cookie的API
如果服务端想向浏览器端写Cookie,只有通过Http。
Http设置Cookie
假设URL为www.itthink.tech/login 的服务想在浏览器端种下两个Cookie,
Cookie 1:
名字:token
值:abc789659hjr735
保存时间:一个月,假设一月后的时间是
所属域:所有itthink.tech和其子域名都可以访问这个Cookie
所属路径:所有路径都可以访问这个Cookie
安全:仅在Https协议下才可发送到服务端
安全:禁止Javascript访问和修改该Cookie
安全:不可作为第三方Cookie发送到服务端
Cookie 2:
名字:uid
值:hr6gac25gfs6dw56
保存时间:半年
所属域:所有itthink.tech和其子域名都可以访问这个Cookie
所属路径:所有路径都可以访问这个Cookie
安全:仅在Https协议下才可发送到服务端
安全:禁止Javascript访问和修改该Cookie
安全:不可作为第三方Cookie发送到服务端
要种上面两个Cookie,通过Http来设置的话,需要在响应头中加上如下内容: 假设已有响应头:
HTTP/2.0 200 OK
Content-Type: text/html
要种Cookie的话则变成:
HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: token=abc789659hjr735; domain=.itthink.tech; path=/; expires=Thu, 10 Nov 2022 02:58:20 GMT; secure; httponly; samesite=lax
Set-Cookie: uid=hr6gac25gfs6dw56; domain=.itthink.tech; path=/; expires=Sun, 09 Apr 2023 02:59:09 GMT; secure; httponly; samesite=lax
可以看到Set-Cookie后面的格式和设置document.cookie
的格式一致。
服务端开发人员一般不太需要像这样去设置Cookie,服务器都会提供对应的API。
Http读取Cookie
Cookie由浏览器负责存储,当有符合条件的请求时,浏览器会将Cookie放入Http请求头中,继续使用上面的例子,Http请求头如下:
Cookie: token=abc789659hjr735; uid=hr6gac25gfs6dw56
可以看到,和Javascript的document.cookie
API一样,Http中可以拿到所有允许获取的Cookie的名字和值,但是无法获取到Cookie的属性。
XMLHttpRequest(XHR)和Fetch不能读取和操作Cookie
有时我们可能希望通过XHR或者Fetch直接往Http请求中塞入Cookie,这个是不可以的。这一点确实会比较让人迷惑,XHR和Fetch都有设置Header的API,而且Cookie属于Header的一部分,你甚至可以设置自定义的Header,但是就是不能设置Cookie。
有时我们可能希望从XHR或者Fetch的Response中获取Cookie,这也是不可以的。我们可以从Response中获取除Set-Cookie
之外的任何Header。
这样做可以确保在正常情况下,服务器端得到的Cookie一定是浏览器中真正的Cookie,不可以用Javascript模拟。服务端向浏览器中设置的Cookie一定是经过浏览器处理后通过document.cookie
来获取。
以上就是操作Cookie的所有API了。
Cookie特性详解
大家照着上面的例子去写Cookie相关的代码,代码的运行结果很有可能和你预期不一样,这时候往往是Cookie的特性在作怪。
如果你对Cookie一无所知,那可以先看一下下面的图:
这幅图说明了三个事实:
- Cookie可以通过Http响应来种到浏览器中
- Cookie可以通过Javascript种到浏览器中
- Cookie可以跟随Http请求发送到服务端
相信如果你没有专门研究过Cookie,那对它的了解应该就是这些了,这些确实就是Cookie最核心的功能,但是要让核心功能安全放心的运行,需要有各方面的考虑:
- 给Cookie设计一种数据格式
- 设计操作Cookie的API
- 由于浏览器环境的特殊性,需要考虑数据的隔离、生命周期与安全
上面已经大概讲过了Cookie的API的大体形式,下面咱们说一下数据格式和具体的属性。
Cookie数据是以Name Value键值对的形式进行组织的,Name即某个Cookie的名字,另外和每个Cookie相关的还有若干属性,这些属性用来设定具体Cookie的隔离性、生命周期、安全性。
每个Cookie键值对都可以有如下属性,点击查看详细介绍: domain
path
expires
max-age
httponly
secure
以下新属性由谷歌推出,还没有成为正式规范,但已经被主流浏览器广泛实现: samesite
以下新属性由谷歌推出,还没有成为正式规范,只有较新的谷歌浏览器才支持:
sameparty
partitioned
以下属性已经由谷歌提出多年,但是并没有被其它浏览器广泛的支持
priority
以上这些属性是我们理解Cookie的关键。咱们一一道来。
Cookie标准
在Web世界,技术的发展主要由几大IT厂商和一些标准组织主导,为了更好的互联互通,IT厂商会遵从标准协议来进行产品的开发。W3C负责HTML、CSS的标准化,ECMA国际负责Javascript的标准化。而我们今天要说的Cookie其标准除了第一版非正式标准由网景公司制定外,后续的版本均是由IETF组织制定,IETF先后经历了RFC2109 RFC2965,到2011年的RFC6265,现在最新版的草案已经于2020年由谷歌提出。RFC6265是目前被广泛实现的Cookie标准。
IETF虽然制定了规范,但是各个浏览器厂商并不一定会严格按照规范执行。
Cookie的历史
1994年,Cookie最初是网景公司的员工开发,目的是为了开发电子商务网站的购物车功能,有了Cookie就可以对用户进行标识,从而记录他想买哪些商品。 所以Cookie一开始就是为了追踪用户而设计的,而且当时所有的浏览器都会静默地接收Cookie,Cookie被默默地用于夸站点用户追踪,用户毫不知情。直到1996年,Cookie的存在被曝光,由于Cookie被用来跨站点追踪用户而导致用户隐私泄漏的风险,在后来的几年里,Cookie机制受到了密集地审查。 这就直接促使IETF(Internet Engineering Task Force)开始制定正式的Cookie规范。IETF想禁用第三方Cookie,除非用户选择允许使用。 但是领头的浏览器厂商都没有听从这个建议,第三方Cookie一直可以被使用。 因此Cookie从诞生开始就伴随着用户隐私泄漏的问题,不过它也确实让广告业变得更兴盛。