身份验证里,有三个很容易混淆的概念。
- 身份声明(Identity Claim) :我是谁?我是
hzh。 - 身份认证(Authentication) :你怎么证明你确实是
hzh? - 权限授权(Authorization) :确定你是
hzh之后,你可以做什么?
比如,用户说"我是 hzh"只是一句声明;输入密码、短信验证码或使用指纹来证明自己,才是在做认证;认证通过后能不能看订单、能不能退款,则属于授权。
登录成功后,问题才刚刚开始
认证通过之后,真正的问题才出现:
下一次 HTTP 请求到来的时候,服务器怎么知道它仍然来自刚才已经认证的
hzh?

为什么 HTTP 会有这个问题?
场景是这样的:hzh 输入账号密码登录商城,服务器核验成功后返回"登录成功"。五分钟后,hzh 请求 /orders。

问题在于:第二次请求在应用层面是一次独立请求。即使底层连接可能被复用,服务器也不会自动知道这个请求还属于刚才登录的用户。
除非请求带上某种可验证的信息,证明它和第一次登录建立的身份有关。
所以,一个完整的机制通常是:
- 客户端提交认证材料,例如密码。
- 服务端验证认证材料。
- 服务端签发后续请求要用的凭据。
- 客户端在每个需要身份的请求中携带该凭据。
- 服务端验证凭据,并恢复或确认用户身份。
- 服务端再根据身份、资源和操作判断权限。
登录时完成的是"你是谁"的认证;后续请求一般不需要重新输密码,而是通过验证凭据来确认:这个请求是否仍然代表刚才认证过的用户。
换句话说,认证建立了身份,后续凭据维持了请求 -> 身份之间可信的关联。
一个可验证的凭据,至少要具备三种能力:
- 身份关联 :能关联到
hzh。 - 可验证性 :攻击者不能随便编造一个值就冒充
hzh。 - 时效控制:过期或退出登录后,应该能够失效。
Cookie + Session:最经典的方案
那我们用什么来维持这个身份?一个经典方案是 Cookie + Session。

这里先记住一句话:
Cookie 负责携带,Session 负责记录。
Cookie 是浏览器携带数据的一种机制;Session 则是服务端保存的会话状态。它们配合起来,才能完成登录态的维持。
一个完整的请求流程
1. 登录

用户提交账号密码,服务端验证通过后:
- 创建一条 Session,例如
session_7f3...。 - 把它和用户身份、过期时间等信息存到服务端。
- 把 Session ID 通过
Set-Cookie发给浏览器。
2. 之后查看订单

浏览器之后访问 /orders 时,会自动带上 Cookie 中的 sid。服务端用这个 sid 查找 Session,找到对应用户后,才知道请求来自谁。
需要注意:
- Cookie 本身不是认证机制。它只是浏览器自动携带数据的方式。
- Session 也不必放在单台应用内存中。有多台后端服务器时,可以把 Session 放到 Redis 等共享存储;否则第一次请求落到服务器 A,第二次落到服务器 B,B 就查不到登录状态。
- 认证成功后应该重新生成 Session ID 。否则攻击者如果提前让用户带上一个已知 Session ID,就可能在用户登录后利用它冒充用户,这叫会话固定攻击。
Session ID 必须具有足够高的随机度。服务器不相信 Cookie 里自称的用户,而是用这个随机 ID 查找自己保存的记录。
所以可以做到:
text
删除服务端的 session_7f3...
-> 浏览器发送旧 Cookie
-> 服务端查不到有效会话
-> 登录失效
退出登录时发生什么?
如果用户退出登录后,浏览器意外又发送了旧的 sid,服务端该怎么处理?
- 用户请求
POST /logout,携带sid=session_7f3...。 - 服务端删除或标记
session_7f3...失效。 - 服务端通过
Set-Cookie指示浏览器删除 Cookie,这是辅助动作。 - 旧
sid再出现时,服务端查不到有效 Session,返回401 Unauthorized。
真正让旧凭据失效的关键是第 2 步:服务端不再认可这条 Session。浏览器是否成功删掉 Cookie,不能作为唯一依赖。
Token、Session 和 JWT 到底是什么关系?
说到"后续请求带的凭据",经常会遇到 Token 和 JWT。
Token 是"后续请求可出示的凭据"这一类东西;JWT 是 Token 可能采用的一种具体数据格式。
Session ID 本身也可以看成一种 Token。它只是一串随机值,具体身份信息在服务端保存。
所以,Token 和 JWT 不是两个平级的认证方案,而是"类别"和"一种具体实现"的关系。
先别急着把 Token 和 JWT 当成两个对立选项。它们更像是"凭据"与"凭据格式"的关系。
接下来可以从两个角度看:凭据由谁保存身份信息 ,以及服务端收到凭据后如何验证它。


两种常见的 Token 设计
1. 不透明 Token(Opaque Token)
text
客户端: "r4nd0m-long-secret"
服务端: token -> { userId: alice, expiresAt, scopes }
不透明 Token 的结构本身没有业务意义。它和 Session ID 很像:服务端需要通过 Token 查询状态,才能知道它代表谁、有没有过期、能做什么。
优点是服务端可以随时吊销它;代价是每次验证通常都需要查状态。
2. JWT
text
客户端: header.payload.signature
服务端: 验签并校验声明 -> 读取受信任的声明
JWT 的 Payload 可以包含例如:
js
{
"sub": "hzh",
"exp": 12345678910,
"scope": "orders:read"
}
服务端验证 JWT 时,除了验签,还需要检查允许的签名算法,以及 exp、iss、aud 等声明是否符合预期。全部通过后,服务端才能信任其中的身份和权限信息。
签名能证明:这些声明是持有对应密钥的一方签发的,且传输途中没有被篡改。
但必须注意:
JWT 的 Base64URL 编码不是加密。
普通签名 JWT 的内容,拿到它的人通常都能解码查看。签名提供的是完整性 和来源验证,不是保密性。所以不要把密码、银行卡号等特别敏感的信息放进 JWT Payload。
JWT 也不是天然"无状态"或更安全:如果要立刻吊销某个 JWT、强制用户下线,服务端通常仍需要维护黑名单、版本号或其他状态。它一旦被窃取,也可能在过期前被冒用。
Cookie 和 Token 并不冲突
Cookie 和 Token 并非互斥关系,它们解决的问题不同:
- Cookie / Authorization Header:凭据怎样从客户端到服务端。
- Session ID / Opaque Token / JWT:凭据具体是什么,以及服务端怎样验证它。
例如,JWT 可以放在 Authorization Header 中,也可以放在 Cookie 中;Session ID 通常放在 Cookie 中。选择哪种方式时,还要结合浏览器自动携带 Cookie、CSRF 风险、XSS 风险和具体业务场景来判断。
总结
可以把整件事记成一句话:
认证解决"你是谁";授权解决"你能做什么";Session 或 Token 解决"后续请求如何证明它还是你"。
无论选择 Cookie + Session、不透明 Token 还是 JWT,核心都一样:让服务器能够安全地验证请求携带的凭据,并据此恢复用户身份,再决定是否允许这次操作。