Skip to content

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 编码组成,分为:头部+负载+签名

txt
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

头部:

json
{
  "alg": "HS256", //加密算法类型 Algorithm
  "typ": "JWT" //类型,是 JWT
}

负载:

json
{
  "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 更安全。

下一个时代

量子计算机对于非对称密码有很强的威胁

所以:加大参数 + 对称加密哈希