前言

Cookie 和 LocalStorage 是非常基础的东西. 我是学编程后, 第 3 年才开始写博客的, 所以很多在第 1, 2 年学的知识完全都没有记入下来. (比如 C#, JS 语法等等)

Cookie 和 LocalStorage 也是其中的一个. 今天就补上呗.

参考:

面试不再怕:史上最全的cookie知识点详解

cookie 详解

Cookie 的作用

HTTP 是无状态的. 前一个请求和后一个请求没有任何关联. 服务端无法判断是同一个 "人" 发出的请求. 这就导致了很多功能没有办法实现. 比如用户登入.

要解决这个问题不能从 HTTP 协议着手, 那只能靠游览器搞一些额外的潜规则了. Cookie 就是这么一个存在.

Cookie 如何实现 "有状态" 的 HTTP

上面提到了游览器的潜规则. 它的过程是这样的.

当游览器发请求给服务端时, 服务端可以在 response 的 header 里加入一个特别的 header 叫 "Set-Cookie"

当游览器接收 response 时会看看有没有这个特别的 header, 如果有, 那就表示服务器想搞一个 "状态". 比如: Set-Cookie : "key=value". 游览器会把这个 key value 记入起来.

在下一次游览器发请求给服务端时, 游览器会把之前记入起来的 key value 放入 header "Cookie" 中.

通过这样的 "潜规则", 游览器和服务端就利用 HTTP 协议的 header 让原本没有状态的 HTTP 变成了 "有状态".

其实 HTTP 只是一种通信协议. 只要在内容上做出规则. 双边是很容易 "认出" 对方的. Cookie 只是游览器替我们封装好的一个方式而已.

比如 Mobile App 就没有 Cookie 但依然可以靠 HTTP + bearer token 来实现 OAuth 登入.

服务端 "Set Cookie" Header

ASP.NET Core

HttpContext.Response.Cookies.Append("Key", "Value");

效果

这个就是一个最简单的服务端 response with Cookie

一个 Set-Cookie 表达一个 key value. 如果想返回多个. 那么就返回多个 header "Set-Cookie".

HttpContext.Response.Cookies.Append("Key", "Value");
HttpContext.Response.Cookies.Append("Key1", "Value1");

游览器 "Cookie" Header

当游览器接收到服务端返回的 Cookie 以后就会记入起来.

下一次发送请求就会把这些 Cookie 发送出去.

Cookie 的体积

由于游览器每一次请求都会把 Cookie 发到服务端. 所以 Cookie 不可以太大. 不然会影响网速.

不同游览器有不同的标准, 但大部分是每个 domain 只能有 1xx 个 Cookie, 每一个最多 4kb.

总之, 尽可能用的少就对了.

Cookie 的各种配置

Cookie 本质上就是一个 header value. 也就是一个字符串. 但它是有 format 的. 它里面其实表达了很多东西. 不仅仅只是 key value. 我们一个一个看.

key & value

最基本的就是 key value.

cookie: Key=Value; Key1=Value1

通过等于 = 把 key value 分开. 通过分号 ; 把多个 key value 分开. 这就是一个基本的 format.

key

key 不可以包含一些特殊符号. 比如 等于, 逗号, 分号, 空格, 等等. 理所当然丫, 不然游览器要怎么 split 呢.

想要了解详情的可以看这篇: Stack Overflow – What are allowed characters in cookies?

通常我是建议取这种 key 的名字就要顺风水, 不要搞一些奇奇怪怪的符号. 尽可能用 a-z 配 hypen 或 underscore 就好了.

ASP.NET Core 如果放入了不合法的 key name, 它会直接报错.

value

value 就不可能避开各种符号了. 它的解决方法是 encode.

在 ASP.NET Core, cookie value 会被 encode

HttpContext.Response.Cookies.Append("Key", "= ,;");

效果

encode 的方式是 JS 的 encodeURIComponent

expires & max-age

Cookie 有一个过期机制. 当 Set-Cookie 时可以指定一个有效期.

当游览器发现 Cookie 过期后, 它就会删除掉. 服务端也是通过这种方式来实现删除 Cookie 的哦.

如果没有指定有效期, 游览器会在关闭的时候直接删除掉 Cookie, 所以想 Cookie 持久就必须设定 expires 或 max-age

expires

HttpContext.Response.Cookies.Append("Key", "value", new CookieOptions
{
Expires = new DateTimeOffset(2023, 1, 9, 5, 55, 0, 0, TimeSpan.FromHours(8))
});

指定 Cookie 过期时间为 2023年 1月9号 5点 55分 +08:00

虽然 ASP.NET Core 支持 timezone 但其实 Cookie 本身是不支持的, 这里是 ASP.NET Core 替我们做了转换.

游览器接收的是 UTC 时间. 相等于 JavaScript 的 new Date().toUTCString()

max-age

相比于 expires 提供一个绝对时间, max-age 则是提供一个相对时间. 只是另一个表达手法而已. 游览器都明白你的意思.

HttpContext.Response.Cookies.Append("Key", "value", new CookieOptions
{
MaxAge = TimeSpan.FromSeconds(30)
});

它表示从现在开始 30 秒后这个 Cookie 失效.

注: 它的单位是 second(秒) 哦. 不是 ms(毫秒) 哦 (而已不支持小数点. 所以没有 100ms 0.1s 这种冬冬)

HttpOnly

上面我们都只谈到服务端 Set Cookie 和游览器 send Cookie. 其实 JavaScript 也是可以读写 Cookie 的哦.

如果服务端不希望 Set Cookie 被 JavaScript 读取, 那么可以附上一个 "httponly". 这样 JS 就读取不到了 (但游览器是可以读取到的...废话)

Secure

secure 表示, 这个 Cookie 只允许在 HTTPS 加密通信中才可以使用.

Samesite

samesite 是一个比较新的东西 (其实好多年了...), 它是用来防 CSRF 的.

从前没有 samesite 机制, 相等于现在设定 samesite=none.

游览器在发送跨域请求 (ajax) 时会带上 Cookie. 这导致了许多安全隐患. 虽然可以用 Anti-Forgery Tokens 来防御.

但是有很多开发人员不太注重安全就没有做. 后来游览器决定修改这个机制. 就有了 samesite

samesite=lax 是当前默认的. 跨域时只有 GET 请求会附上 cookie. POST 不会.

samesite=strict 表示不管 GET, POST 只要是跨域一概不允许发这个 Cookie.

Path

path=/ 表示任何路径都附上 Cookie.

path=/about 表示这个 Cookie 只有在请求 /about 这个路径才附上.

如果没有声明, 那就表示是当前 request 路径.

注: 只能 set parent path 哦, 比如 request 是 domain.com/a/b/c

默认就是 /a/b/c, 我们可以 set 成 /, /a, /a/b. 但是不能 set 成 x/y/z 或者 /a/b/c/d

Domain

服务端当然可以返回任何 Domain 值, 只是游览器不会处理而已...哈哈.

domain 是用来设置 subdomain 访问的.

domain=.example.com 前面加了一个点, 表示这个 Cookie 适用于 example.com 和其下所有 sub domain.

domain=sub.example.com 表示 Cookie 只用于 sub.example.com 访问.

如果没有声明, 那么 Set-Cookie 就表示当前 request 的 domain.

注: 只能 set parent domain. 比如 request 是 sub.domain.com 可以 set 成 .domain.com

完整版

HttpContext.Response.Cookies.Append("Key", "value", new CookieOptions
{
Domain = ".example.com",
Path = "/",
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Lax,
Expires = DateTimeOffset.Now.AddHours(1),
MaxAge = TimeSpan.FromSeconds(TimeSpan.FromHours(1).TotalSeconds),
});

默认值

JavaScript Cookie

上面提到的都是服务端和游览器间的 Cookie. 其实 JS 也是可以读取到这些 Cookie 的. (只要 Cookie 没有指定 httponly)

Read Cookie

console.log(document.cookie);

效果

它返回的是一个 string. 里头包含了所有能访问到的 Cookie key and value.如果我们只想获取某些 key 那么需要自己从字符串中提取.

另外, JS 只能读取到 key value 而已. 像 expires 这些其它信息是无法读取到的.

Write Cookie

JS 也是可以创建 Cookie 哦

document.cookie = `key2=value2; max-age=3600; secure; path=/;`;
document.cookie = `key3=value3; max-age=3600; secure; path=/;`;

每一次 document.cookie = 都会创建一个 key value 的 Cookie.

调用多次就创建多个. 这里的语法很不直观. 如果改成 document.cookie.add('...') 才比较符合它的行为.

如果要删除 Cookie 就设置 max-age=-1 或者 expires=一个过期的时间 (注: 确保 key, domain, path 相同哦)

value 记得用 encodeURIComponent encode 一下

expires 则用 toUTCString()

附上一个以前写的读写 Cookie

function setCookie(
key: string,
value: string,
config?: {
domain?: string;
path?: string;
expires?: Date;
maxAge?: number;
secure?: boolean;
sameSite?: 'none' | 'lax' | 'strict';
}
): void {
const { expires, maxAge, domain, path = '/', secure = true, sameSite } = config ?? {};
const strings: string[] = [];
const keyValue = `${key}=${encodeURIComponent(value)}`;
strings.push(keyValue);
if (expires) {
strings.push(`expires=${expires.toUTCString()}`);
}
if (maxAge !== undefined) {
strings.push(`max-age=${maxAge}`);
}
if (domain !== undefined) {
strings.push(`domain=${domain}`);
}
strings.push(`path=${path}`);
if (secure) {
strings.push('secure');
}
if (sameSite !== undefined) {
strings.push(`samesite=${sameSite}`);
}
document.cookie = strings.join('; ');
} function getAllCookie(): Record<string, string> {
const allCookie: Record<string, string> = {};
const cookieString = document.cookie;
for (const keyValue of cookieString.split(';')) {
const [key, value] = keyValue.trim().split('=');
allCookie[key] = decodeURIComponent(value);
}
return allCookie;
} function getCookie(key: string): string | null {
const allCookie = getAllCookie();
return allCookie[key] ?? null;
} function deleteCookie(key: string, config?: { domain?: string; path?: string }): void {
const { domain, path = '/' } = config ?? {};
const strings: string[] = [];
const keyValue = `${key}=value`;
strings.push(keyValue);
strings.push(`max-age=-1`);
if (domain !== undefined) {
strings.push(`domain=${domain}`);
}
strings.push(`path=${path}`);
document.cookie = strings.join('; ');
}

第三方 Cookie

参考: 知乎 – 当浏览器全面禁用三方 Cookie

做 marketing 的人唯一可能听过跟技术相关的词就是第三方 Cookie. 因为前几年苹果为了用(进)户(军)隐(广)私(告) 决定静止第三方 Cookie.

什么是第三方 Cookie 呢?

上面有提到, 如果服务端返回 Set-Cookie 其它 domain 游览器是不理的

JS document.cookie = 其它 domain 也是无效的.

但是如果有一个 <img src="其它 domain" > 而这个请求有 Set-Cookie 其它 domain 确实可以的.

这个就是所谓的第三方 Cookie 了. 在 a.com 请求 b.com 得到 b.com 的 Cookie. 这个 Cookie 就是第三方的.

为什么要第三方 Cookie?

第三方 Cookie 是用来做广告的. 上面的例子

a.com 要想识别 "一个人" 就给他 Cookie 咯. 这个叫第一方

b.com 是广告公司, 它也想识别 "一个人" 在 a.com, 那么它也需要 Cookie 咯.

b.com 无法使用 a.com 的 Cookie, LocalStorage 等等. 所以它只能想办法让这个人访问 b.com 这样才能返回 b.com 的 Cookie.

所以就用到了 img src 这类的方式.

苹果禁止了第三方 Cookie

苹果禁止第三方 Cookie 后, b.com (广告公司) 就无法通过 img src 在 a.com 创建出 b.com 的 Cookie 了.

整个 tracking 就失败了. 而为了解决这个问题, 目前各大广告公司会要求网站创建 a.com 的 Cookie 并且把这个数据发送到广告公司的服务器.

本来是前端干的事, 变成了服务端... 当然这对于网站安全是比较危险的. 毕竟前端插入广告公司的代码不会有非常大的安全隐患. 但如果是服务端必须安装广告公司的 dll 就比较危险了.

LocalStorage 介绍

Cookie 体积小, 不适合存放大数据. 于是 LocalStorage 就诞生了.

它和 Cookie 本质上是不同的东西, 也不是互相替代的.只是它俩有点雷同, 所以经常会放到一起聊.

和 Cookie 的雷同和区别

1. LocalStorage 不会发送到服务端, 服务端也无法创建 LocalStorage. 它完全就是前端的东西.

2. 它们都是用 key value 来存资料

3. Cookie 体积很小, LocalStorage 很大 (好像是 5mb)

4. 它们都是跨域保护, 不同 domain, subdomain 都不可以访问到 LocalStorage

5. Cookie 有 expires, LocalStorage 没有

LocalStorage 使用

set key value

localStorage.setItem('key', 'value');

value 必须是 string. 不需要 encode.

get value

localStorage.getItem('key');

找不到返回 null 而不是 undefined 哦

remove key

localStorage.removeItem('key');

Cookie 通过 set expires 来实现删除. LocalStorage 则有删除接口

remove all

localStorage.clear()

一个方便的接口, 直接删除所有 key

get all keys

const keys = Object.keys(localStorage);
for (const [key, value] of Object.entries(localStorage)) {}

get value by index

const value = localStorage.key(0);

这个接口最好是不要用, 因为 localStorage 的顺序是不可靠

localStorage.key(0);
// 相等于
localStorage.getItem(Object.keys()[0]);

get length

console.log(localStorage.length);

返回当前有多少 keys.

把 localstorage 当 object 调用

get

const value = localStorage['key']; // 找不到返回 undefined 而不是 null 哦
// 相等于
const value = localStorage.getItem('key') ?? undefined;

set

localStorage.key = true;
// 相等于
localStorage.setItem('key', String(true));

自动强转成 string

delete

delete localStorage.key;
// 相等于
localStorage.removeItem(key);

LocalStorage with Expiration

LocalStorage 最大的不方便就是它没有过期机制.

一个常见的 workaround 是把 expires 写入 value 里. 比如

localStorage.setItem('key', 'value; max-age=3600')

当然, 这完全是个人的实现, 要用什么规范都可以. 你可以模拟 Cookie 的方式, 也可以把 value 做成 JSON

localStorage.setItem('key', JSON.stringify({
value: 'value',
expires : new Date()
}))

重点是在 getItem 时需要从 value 中取出时间并且检查过期与否等后续的操作.

这里附上我以前写的一个版本, 支持 expires

interface ExpirableLocalStorage extends Storage {
setItem(key: string, value: string, expireDate?: Date): void;
} interface Data {
expirationDate: Date;
value: string;
}
type FromParseJsonObject<T> = {
[P in keyof T]: T[P] extends Date
? string
: keyof T[P] extends never
? FromParseJsonObject<T[P]>
: T[P];
}; function isData(value: object): value is FromParseJsonObject<Data> {
return Object.keys(value).length === 2 && 'expirationDate' in value && 'value' in value;
} const internalExpirableLocalStorage: ExpirableLocalStorage = {
setItem(key: string, value: string, expirationDate?: Date): void {
if (!expirationDate) {
localStorage.setItem(key, value);
} else {
const data: Data = {
expirationDate,
value,
};
const jsonValue = JSON.stringify(data);
localStorage.setItem(key, jsonValue);
}
}, getItem(key: string): string | null {
const maybeJsonValue = localStorage.getItem(key);
if (maybeJsonValue === null) return null;
try {
const maybeData = JSON.parse(maybeJsonValue);
if (isData(maybeData)) {
if (new Date(maybeData.expirationDate) <= new Date()) {
this.removeItem(key);
return null;
}
return maybeData.value;
} else {
return maybeJsonValue;
}
} catch {
return maybeJsonValue;
}
}, get length(): number {
return Object.keys(localStorage).filter(key => this.getItem(key) !== null).length;
}, key(index: number): string | null {
return (
Object.keys(localStorage)
.map(key => this.getItem(key))
.filter(v => v !== null)[index] ?? null
);
}, removeItem(key: string): void {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
},
}; const standardKeys = ['setItem', 'getItem', 'length', 'key', 'removeItem', 'clear'];
export const expirableLocalStorage = new Proxy(internalExpirableLocalStorage, {
get(target, prop: string) {
if (standardKeys.includes(prop)) return target[prop];
return target.getItem(prop) ?? undefined;
},
set(target, prop: string, value) {
if (standardKeys.includes(prop)) {
target[prop] = value;
return true;
}
target.setItem(prop, String(value));
return true;
},
deleteProperty(target, key: string) {
target.removeItem(key);
return true;
},
});

BOM – Cookie 和 LocalStorage的更多相关文章

  1. 用cookie实现localstorage功能

    在项目中需要利用到html5的localstorage.但在利用这个属性的时候却发现无法达到预定目标.经过不断的检查及排除,最后发现原因所在: 项目中使用的浏览器是支持localstorage的,但是 ...

  2. 本地存储 cookie,session,localstorage( 一)基本概念及原生API

    http://www.w3school.com.cn/html5/html_5_webstorage.asp http://adamed.iteye.com/blog/1698740 localSto ...

  3. 浏览器本地储存方式有哪些?cookie、localStorage、sessionStorage

    现阶段,浏览器提供的储存方式常用的有三种,cookie.localStorage.sessionStorage 1.cookie 概念:cookie 是浏览器中用于保存少量信息的一个对象 基本特征: ...

  4. jquery访问浏览器本地存储cookie,localStorage和sessionStorage

    前言:cookie,localStorage和sessionStorage都是浏览器本地存储数据的地方,其用法不尽相同:总结一下基本的用法. 一.cookie 定义: 存储在本地,容量最大4k,在同源 ...

  5. JS 详解 Cookie、 LocalStorage 与 SessionStorage

    基本概念 Cookie Cookie 是小甜饼的意思.顾名思义,cookie 确实非常小,它的大小限制为4KB左右.它的主要用途有保存登录信息,比如你登录某个网站市场可以看到"记住密码&qu ...

  6. Cookie、LocalStorage 与 SessionStorage的区别在哪里?

    基本概念 Cookie Cookie 是小甜饼的意思.顾名思义,cookie 确实非常小,它的大小限制为4KB左右.它的主要用途有保存登录信息,比如你登录某个网站市场可以看到“记住密码”,这通常就是通 ...

  7. 深入了解浏览器存储:对比Cookie、LocalStorage、sessionStorage与IndexedDB

    摘要: 对比Cookie.LocalStorage.sessionStorage与IndexedDB 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有. 前言 随着移动网络的发展与演化,我 ...

  8. session,cookie,sessionStorage,localStorage的区别及应用场景

    session,cookie,sessionStorage,localStorage的区别及应用场景 浏览器的缓存机制提供了可以将用户数据存储在客户端上的方式,可以利用cookie,session等跟 ...

  9. 缓存session,cookie,sessionStorage,localStorage的区别

    https://www.cnblogs.com/cencenyue/p/7604651.html(copy) 浅谈session,cookie,sessionStorage,localStorage的 ...

  10. js中cookie,localStorage(sessionStorage)的存取

    一.cookie (原生的不好用,自己简单封装) 1. 存cookie的方法: function setCookie(c_name,value,expiredays) { var exdate=new ...

随机推荐

  1. 效率工具RunFlow完全手册之进阶篇

    欢迎来到RunFlow手册的进阶篇,如果您还不了解RunFlow,建议先阅读我们的基础篇. (Solo 社区投稿) 搜索文件 按文件大小过滤,添加 len 参数,比如:len:1kb-2kb,len: ...

  2. Mysql密码安全策略修改

    Mysql5.7默认有密码安全策略,密码安全级别要求比较高,在测试环境中使用起来不方便,本经验将介绍如何修改Mysql的密码安全策略,解决ERROR 1819错误. 1:首先使用root用户连接mys ...

  3. [oeasy]python0135_命名惯用法_name_convention

    命名惯用法 回忆上次内容 上次 了解了isidentifier的细节 关于 关键字 关于 下划线   如何查询 变量所指向的地址? id   如何查询 已有的各种变量? locals   如果 用一个 ...

  4. Python爬虫(5-10)-编解码、ajax的get请求、ajax的post请求、URLError/HTTPError、微博的cookie登录、Handler处理器

    五.编解码(Unicode编码) (1)GET请求 所提方法都在urllib.parse.路径下 get请求的quote()方法(适用于只提交一两个参数值) url='http://www.baidu ...

  5. PHP 高性能框架 Workerman 凭什么能硬刚 Swoole ?

    大家好,我是码农先森. 一次偶然看到了国外某机构针对 PHP 周边生态框架及扩展的性能测试排行榜,看到 Workerman 竟遥遥领先 Swoole.在我们 PHP 程序员现有的认知里,Swoole ...

  6. 阅读翻译Mathematics for Machine Learning之2.8 Affine Subspaces

    阅读翻译Mathematics for Machine Learning之2.8 Affine Subspaces 关于: 首次发表日期:2024-07-24 Mathematics for Mach ...

  7. python global将结果存储起来给另外一个文件对象使用

    python global将结果存储起来给另外一个文件对象使用 使用场景: 在aaa.py文件里面操作数据生成结果C 然后再在bbb.py文件里面使用C 下面是aaa.py代码: #!/usr/bin ...

  8. 加油,为Vue3提供一个可媲美Angular的ioc容器

    为什么要为Vue3提供ioc容器 Vue3因其出色的响应式系统,以及便利的功能特性,完全胜任大型业务系统的开发.但是,我们不仅要能做到,而且要做得更好.大型业务系统的关键就是解耦合,从而减缓shi山代 ...

  9. 【Vue】Re22 Axios

    Axios[AJAX I\O System] 创建案例项目并且安装Axios npm install axios --save 接口测试网址: http://httpbin.org/ 案例提供的数据地 ...

  10. 苹果系统Mac升级后之前的网络软件不可用——Mac系统维护——Mac系统升级后软件报错——mac系统升级后导致软件兼容报错

    ========================================== 博士同学最近联系我,说是自己的mac系统升级后之前可以用的网络软件不可用使用了,由于平时工作需要,这个网络软件如果 ...