RESTful API
名称来源于 REST(Representational State Transfer,表述性状态转移),这是 Roy Fielding 在 2000 年的博士论文中提出的一种软件架构风格
表述性:指资源可以用多种形式表述,如 JSON、XML。API 通过传输资源的表述来操作资源,而不是直接操作资源本身
状态转移:指通过请求来改变资源的状态
REST 强调资源为中心的设计。每个资源有唯一标识(URI),并通过标准的 HTTP 方法进行操作
HTTP 协议无状态
服务器默认情况下无法区分两个连续的请求是否来自同一个用户,或者同一个用户之前的操作是什么。
这就像一个“健忘”的服务员,每次你跟他说话,他都不知道你是谁,也不知道你之前点过什么菜。
无状态指:服务器不会记录客户端的状态,并不是指每次请求都是函数式编程的无副作用,是指服务器不能依赖于之前请求保存的上下文
在分布式系统中,无状态似乎是更优的设计哲学
从信息论的角度,状态会扩大系统不确定性的空间(复制、排障都需要更多信息),而无状态,把不确定性压缩到明确的边界里
输出=f(代码,配置,输入,内部状态)
输出=f(代码,配置,输入)
状态越多,系统熵越大。有状态系统的信道噪声更大。
其实和类,要不要写一大堆隐式取成员变量的函数一样。
传统长寿命服务器容易出现:A 机器能跑,B 机器不能跑,本质就是依赖了隐性的内部状态。
有状态实例像一只养了三年的宠物猫:它有历史、有习惯、有病历、有独特状态。
无状态实例像一张刚打印出来的试卷:坏了就重打一张。
无状态不是“没有状态”,而是“状态被集中管理”,把状态从计算节点里剥离出来,放到少数明确、可观测、可备份、可复制的状态系统中。
底层机器必然有状态;上层编程追求“可控地管理状态”。不要让状态在程序结构里到处暗流涌动。
登记住录
主流是两种方式
1、Session+Cookie
2、JWT
都是在无状态协议上建立有状态会话的方法
Session+Cookie
Session:会话
用户登录后,服务器创建一个 Session 对象,把 SessionID 给用户,浏览器把它保存到 Cookie 里,后续所有请求都会附带 Cookie,服务器从 Cookie 里拿 SessionID 找到用户
类似大模型的 API-key,只不过 SessionID 时效性较短
服务器里,SessionID 一般会选择存放在 Redis 中,服务器通过 HTTP 响应头中的 Set-Cookie 指令,把这个 SessionID 发送给用户的浏览器
问题:
1、多服务器同步。如果 A 给你分配 SessionID,然后你又被负载均衡器分配到 B 服务器,B 不认识你。
2、CSRF
CSRF(Cross Site Request Forgery) 一般被翻译为 跨站请求伪造
就是说,你在访问 A 网站时,里面可能包含一个来源于 B 网站的代码,B 网站的代码的内容是,向 A 网站发起一个请求。你如果点击了或者加载了,你就会自己向 A 网站发送请求,此时,浏览器会默认附带 Cookie,所以被攻击
(1)B 网站服务器本身不参与攻击,只是提供恶意代码,操作是你自己引发的
(2)请求默认附带 Cookie 是问题根源
JWT
JWT - JSON Web Tokens
和前面类似,只是登录后,返回的是 Token,客户端把 Token 存储起来,然后每次 Header 中附带 Token,服务器检查 Token
它可以解决 CSRF 攻击,原因是 Token 放在 localStorage (浏览器本地存储)中。前端框架,通过代码,把 Token 放在请求头里,而不是浏览器默认的行为
当然,如果有人能拿到你的 localStorage,一样能攻击,XSS(跨站脚本攻击)。
优势:
JWT 自身包含了身份验证所需要的所有信息,符合 RESTful 的无状态设计原则,即多个服务器不需要自己记录 SessionID,解决同步问题
JWT 通常是这样的:xxxxx.yyyyy.zzzzz
由三个 base64 编码组成,分为:头部+负载+签名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c头部:
{
"alg": "HS256", //加密算法类型 Algorithm
"typ": "JWT" //类型,是 JWT
}负载:
{
"uid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
"sub": "1234567890",
"name": "John Doe",
"exp": 15323232, // JWT 的过期时间
"iat": 1516239022, // JWT 签发时间
"scope": ["admin", "user"]
}现代登陆方式
| 时代 | 方式 | 安全性 |
|---|---|---|
| 第一代 | 密码 | 容易泄露、复用、被钓鱼 |
| 第二代 | 密码 + 验证器 App 动态码 | 强很多,但仍可能被实时钓鱼 |
| 第三代 | Passkey / 硬件密钥 | 更强,抗钓鱼更好 |
第一代到第二代,增加了 MFA(Multi-Factor Authentication)多因素认证,即账号密码+一个独立的凭证,不仅仅要验证密码,还要验证短信/验证器 App/FaceID
在 iOS 的密码工具里
通行密钥 = Passkey = 第三代
验证码 = 验证器 App = 第二代
验证器 App
机制叫 TOTP:Time-based One-Time Password
大概逻辑是:
1、ChatGPT / OpenAI 给你一个二维码,你用 iPhone 原相机扫描
2、这个二维码里其实藏着一串密钥。包括:
账号:your@email.com
服务:OpenAI
秘密种子:ABCDEF123456...
算法:每 30 秒生成一次 6 位码注意:这里很关键,OpenAI 的服务器会将 账号和种子绑定,记录在服务器里。这一步好比绑定手机号。
3、你的验证器 App 保存这串密钥。
4、以后每隔 30 秒,App 用“当前时间 + 密钥”算出一个 6 位验证码。 (你以后,随时能看到一个 6 位验证码)
5、OpenAI 服务器也有同一串密钥,也能算出当前应该是什么码。
6、你输入的码和服务器算出来的一致,就通过。
你手机本地和 OpenAI 服务器各自用同一个秘密种子,根据时间算出同一个验证码。
Q:为什么必须打开 Apple 的密码工具,而不是在 GPT 里再做一个短信?
A:安全认证和被登陆的应用分离
Q:信任点在哪里?
A:登录者能拿出只有绑定时保存过秘密种子的设备才能生成的验证码。
一句话:将账号绑定手机号,换成了,账号绑定种子。
Passkey
第三代登陆技术
其实它是指 https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API
创建 Passkey 时,先生成密钥对,自己保存私钥,公钥上传到网站服务器
登录时,网站用公钥发起挑战,你在本地解开,网站用公钥验证
安全点:
(1)私钥永远不发给网站
(2)Passkey 和网站域名绑定
Face ID / Touch ID 是本地解锁私钥的方式。
这也是虚拟货币 App 喜欢搞 Passkey 的原因,因为比验证器 App 更安全。
下一个时代
量子计算机对于非对称密码有很强的威胁
所以:加大参数 + 对称加密哈希