BOM – Cookie 和 LocalStorage
前言
Cookie 和 LocalStorage 是非常基础的东西. 我是学编程后, 第 3 年才开始写博客的, 所以很多在第 1, 2 年学的知识完全都没有记入下来. (比如 C#, JS 语法等等)
Cookie 和 LocalStorage 也是其中的一个. 今天就补上呗.
参考:
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
做 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的更多相关文章
- 用cookie实现localstorage功能
在项目中需要利用到html5的localstorage.但在利用这个属性的时候却发现无法达到预定目标.经过不断的检查及排除,最后发现原因所在: 项目中使用的浏览器是支持localstorage的,但是 ...
- 本地存储 cookie,session,localstorage( 一)基本概念及原生API
http://www.w3school.com.cn/html5/html_5_webstorage.asp http://adamed.iteye.com/blog/1698740 localSto ...
- 浏览器本地储存方式有哪些?cookie、localStorage、sessionStorage
现阶段,浏览器提供的储存方式常用的有三种,cookie.localStorage.sessionStorage 1.cookie 概念:cookie 是浏览器中用于保存少量信息的一个对象 基本特征: ...
- jquery访问浏览器本地存储cookie,localStorage和sessionStorage
前言:cookie,localStorage和sessionStorage都是浏览器本地存储数据的地方,其用法不尽相同:总结一下基本的用法. 一.cookie 定义: 存储在本地,容量最大4k,在同源 ...
- JS 详解 Cookie、 LocalStorage 与 SessionStorage
基本概念 Cookie Cookie 是小甜饼的意思.顾名思义,cookie 确实非常小,它的大小限制为4KB左右.它的主要用途有保存登录信息,比如你登录某个网站市场可以看到"记住密码&qu ...
- Cookie、LocalStorage 与 SessionStorage的区别在哪里?
基本概念 Cookie Cookie 是小甜饼的意思.顾名思义,cookie 确实非常小,它的大小限制为4KB左右.它的主要用途有保存登录信息,比如你登录某个网站市场可以看到“记住密码”,这通常就是通 ...
- 深入了解浏览器存储:对比Cookie、LocalStorage、sessionStorage与IndexedDB
摘要: 对比Cookie.LocalStorage.sessionStorage与IndexedDB 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有. 前言 随着移动网络的发展与演化,我 ...
- session,cookie,sessionStorage,localStorage的区别及应用场景
session,cookie,sessionStorage,localStorage的区别及应用场景 浏览器的缓存机制提供了可以将用户数据存储在客户端上的方式,可以利用cookie,session等跟 ...
- 缓存session,cookie,sessionStorage,localStorage的区别
https://www.cnblogs.com/cencenyue/p/7604651.html(copy) 浅谈session,cookie,sessionStorage,localStorage的 ...
- js中cookie,localStorage(sessionStorage)的存取
一.cookie (原生的不好用,自己简单封装) 1. 存cookie的方法: function setCookie(c_name,value,expiredays) { var exdate=new ...
随机推荐
- clickhouse节点重做(节点替换)
测试验证环境: docker容器化部署的4节点2分片和2副本(centos7+clickhouse22.1.3) 172.17.0.6 clickhouse01172.17.0.7 clickhous ...
- C#:进程之间传递数据
一.思路 在Windows程序中,各个进程之间常常需要交换数据,进行数据通讯.常用的方法有 使用内存映射文件 通过共享内存DLL共享内存 使用SendMessage向另一进程发送WM_COPYDATA ...
- 【JavaScript高级01】JavaScript基础深入
1,数据类型 JavaScript将数据分为六大类型,分别为数值类型(number).字符串类型(string).布尔类型(boolean).undefined(定义未赋值).null(赋值为空值). ...
- 把python中的列表转化为字符串
怎么把python中的列表转换为字符串: 1,列表中非字符串的元素的转换 方法一: 使用列表推导式进行转换 1 list=['hello',6,9,'beizhi'] 2 list=[str(i) f ...
- 由delete语句引起的锁范围扩大
由delete语句引起的锁范围扩大 阿里云月报中的一句话,出处:http://mysql.taobao.org/monthly/2022/01/01/ 但是Ghost Record是可以跟正常的Rec ...
- 服务端渲染中的数据获取:结合 useRequestHeaders 与 useFetch
title: 服务端渲染中的数据获取:结合 useRequestHeaders 与 useFetch date: 2024/7/24 updated: 2024/7/24 author: cmdrag ...
- Vue cil路由如何回到初始状态
前景:我们在网页里进入路由的地址后,会发现地址栏中会加上我们的路由地址,这样我就知道当前在哪个位置.但是这样子我们如何手动刷新浏览器,想要浏览器回到根路径的话,是无法直接回去的,因为地址没有更改.再怎 ...
- Fusion Compute install
分区选择默认 配置网络 (使用tab和上下左右 会有红色阴影表示当前选中部分) 密码有复杂度要求 这里输huawei12#$ 一个vrm单节点 两个vrm为主备 FC由vrm与can组成 Vrm提供管 ...
- 【Spring Data JPA】05 方法名限定查询
方法名限定查询 方法名限定查询是对JPQL的再封装 按照SpringData提供的方法名定义方法,不需要配置JPQL语句即可完成查询 在IDEA中都有相应的提示 他会按照方法字符判断 public C ...
- 【Docker】06 部署挂载本地目录的Nginx
1.拉取Nginx镜像: docker pull nginx:1.19 2.生成一个测试的Nginx容器: docker run --rm --name nginx-test -p 8080:80 - ...