从 Cookie 到 Token,登录态到底是怎么维持的

登录一次,之后就不用再输密码,这背后是"登录态"在起作用。

搞懂它挺重要。写接口、调第三方服务、排查"为什么明明登录了却说我没登录",绕来绕去都会碰到它。这篇从头捋一遍,从最早的 Cookie 讲到现在的 Token。

一、HTTP 本身是"失忆"的

先说一个容易被忽略的事实:HTTP 协议本身不记得你是谁。

你发一个请求,服务器处理完就忘了。下一个请求再来,它也不知道这两个请求是同一个人。这是设计使然,不是缺陷。

那"登录态"要解决的,就是在这个前提下,让服务器能认出"你还是刚才那个人"。办法说起来也简单:登录成功后,服务器给你一个凭证,之后你每次请求都把它带上。

举个直观的例子。你去一个不认人的柜台办事,每次都得重新报一遍身份。登录态要做的,就是让柜台给你一张"通行证",之后进门亮一下就行。这张通行证怎么发、怎么防伪、怎么回收,就是不同方案之间的分歧所在。

至于这个凭证长什么样、存在哪、怎么验证,就分出了不同流派。

二、最早的方案:Cookie 加 Session

这是最经典的一套。

用户提交账号密码,服务器验证通过,就在自己这边建一条记录,叫 session。这条记录有个 id,服务器通过响应头 Set-Cookie,把这个 id 发给浏览器。浏览器保存下来,之后每次请求都自动带上它。

python 复制代码
# 用户登录后,服务器返回类似这样的响应头
Set-Cookie: session_id=abc123; Path=/; HttpOnly

服务器收到请求,看一眼 Cookie 里的 session_id,去自己的存储里查,就知道这是谁了。

它有个很实在的好处:状态存在服务器那边,想作废随时能作废。管理员点一下"强制下线",删掉那条 session,用户下次请求就失效了。

代价也很明显。服务器得存这些 session,用户一多,存储和查询都是压力。更麻烦的是多台机器,用户第一次请求打到 A 机器,第二次打到 B 机器,B 上没有这条记录,就认不出来了。于是又要引入共享存储,通常是 Redis,让所有机器查同一份。

顺带说一句,Session 的这份服务端记录,也决定了它的一个天然优势:你能清楚地知道"现在有哪些人登录着"。这对需要管控在线用户的系统来说,是刚需。

三、Token:把状态交给客户端

Token 这条路,思路反了过来。

登录成功后,服务器不再保存状态,而是直接给你一个 token。这个 token 本身就包含了"你是谁"这类信息,并且经过签名。你之后带着它来,服务器验一下签名,就知道你是谁,根本不用查表。

python 复制代码
# 请求时带上 token
headers = {"Authorization": "Bearer eyJhbGciOi..."}

好处很直接。服务器不用存东西,天然支持多机扩展,随便哪台机器都能独立验证。跨服务、跨域也省事。

代价同样有。状态在客户端,服务器想主动作废就难了。你总不能在服务器上一删就完事,因为压根没存。常见的补救是维护一个"黑名单",或者把 token 的有效期设短一点。

四、JWT 到底长什么样

Token 里最常见的一种格式是 JWT。

它由三段组成,用点号隔开。第一段是头部,说明用什么算法签名。第二段是负载,装着真实数据,比如用户 id、过期时间。第三段是签名,用来防篡改。

python 复制代码
# JWT 的结构(示意)
eyJhbGciOiJIUzI1NiJ9 . eyJ1aWQiOjEyMywiZXhwIjoxNzAwMDAwMDAwfQ . xxxxxx
      header                      payload                       signature

这里有个特别容易误解的点:JWT 的 payload 只是做了 Base64 编码,不是加密。

也就是说,任何人拿到这个 token,都能把中间一段解出来看内容。所以千万别往里塞敏感信息,比如手机号、身份证。它能防的是"篡改",防不了"偷看"。

五、凭证放在哪

浏览器场景,Cookie 是最省心的,因为浏览器会自动带上。给关键 Cookie 加上 HttpOnly,脚本就读不到,能防一部分 XSS 攻击。

python 复制代码
Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Lax

脚本场景就不一样了,你得自己管理。用 requests 的话,Session 对象能帮你把 Cookie 存好。

python 复制代码
import requests

BASE = "https://example.com"
s = requests.Session()
s.post(f"{BASE}/login", data={"user": "demo", "pwd": "123"})
s.get(f"{BASE}/profile")   # 自动带上登录态

如果对方用的是 Authorization 头,那就得自己存 token,每次手动带上。

还有一种情况,前端把凭证存在本地存储里。方便是方便,但它能被页面脚本读到,一旦页面被注入恶意脚本,凭证就有暴露风险。相比之下,Cookie 加上 HttpOnly,至少脚本层面拿不到。

六、过期和刷新

凭证不能永不过期,不然泄露了就是永久后门。

所以常见的做法是发两个:一个 access token,有效期短,比如几小时;一个 refresh token,有效期长,比如几周。access 过期了,就拿 refresh 去换一个新的,不用重新登录。

这也解释了一个常见现象:为什么有时候接口突然返回 401。多半就是 access token 过期了,而你的代码没有处理续期。

七、几个容易踩的地方

第一个,把 JWT 当加密用。前面说过,payload 谁都能解,敏感信息别放。

第二个,只存 token,不管过期时间。等报错了才反应过来,其实可以在本地记住过期时刻,提前刷新。

第三个,Cookie 的属性没设对。Domain 或 Path 不对,浏览器根本不会把它带到目标地址;Secure 加上之后,只有 HTTPS 请求才带。这类问题最难查,因为请求看起来"什么都没发错",就是没带上。

第四个,多线程共用一个 Session,会话串了数据。这个细节后面单独展开。

第五个,凭证泄露了都不知道。前面提醒过别往 payload 里塞敏感信息,其实凭证本身也一样。日志里把完整 token 打出来、把 token 拼进 URL,都是很常见的疏忽。

八、那到底该选哪个

没有标准答案,看场景。

如果是单体应用、服务端渲染,Session 就够了,简单直接,还能随时踢人。

如果是前后端分离、要给多个端共用、要跨服务调用,Token 或者 JWT 更顺手,因为它是无状态的,扩展起来省心。

一句话,要强控制就 Session,要好扩展就 Token。搞清自己的需求,再挑,别跟风。

九、串一个流程看一遍

用 requests 分别走一遍两种方式,对比着看更直观。

python 复制代码
import requests

BASE = "https://example.com"

# Cookie 方式:靠会话对象自动带
s = requests.Session()
s.post(f"{BASE}/login", data={"user": "demo", "pwd": "123"})
print(s.cookies.get_dict())        # session_id=...
s.get(f"{BASE}/orders")

# Token 方式:自己存、自己带
r = requests.post(f"{BASE}/api/login",
                  json={"user": "demo", "pwd": "123"})
token = r.json()["token"]
requests.get(f"{BASE}/api/orders",
             headers={"Authorization": f"Bearer {token}"})

区别一目了然。Cookie 方式靠浏览器或会话对象自动带上,开发者几乎不用操心。Token 方式得自己管,存好、带上、过期了还得换。前者省心,后者灵活,代价也就跟着来了。

十、安全上的几个提醒

不管用哪种,有几个细节容易被忽略,出事却都是大事。

给关键的 Cookie 加上 HttpOnly、Secure、SameSite,这三个属性不值钱,但能挡掉不少常见的攻击。HttpOnly 让脚本拿不到,Secure 保证只在 HTTPS 下发送,SameSite 限制跨站携带。

别把凭证放在 URL 里。URL 会被记进日志、浏览器历史,一旦带上凭证就等于到处张贴。要传,就放在请求头或者 Cookie 里。

凭证的有效期别太长。权限给得越大、时间给得越久,泄露的成本就越大。短期凭证配合刷新机制,是成本最低的防护。

十二、两个常被问到的问题

token 能不能一直续下去? 不能。refresh token 本身也会过期,到期还是得重新登录。凭证总得有个终点,只是把重新登录的间隔拉长了而已。

为什么有的服务既要 Cookie 又要 token? 很常见。Cookie 用来维持浏览器侧的会话,token 用来做接口之间的鉴权,两套东西各管一段,并不冲突。

十三、小结

登录态这件事,本质是在无状态的 HTTP 上,想办法维持一份"你是谁"的共识。Cookie 把状态放服务端,Token 把它放客户端,各有取舍。

把这层搞懂之后,再去调接口、排故障,很多原来觉得玄学的问题,一下就说得通了。


互动时间:你在项目里用的是 Cookie 还是 Token?踩过哪种坑?评论区聊聊 👇


声明:本文为原创技术分享,请遵守相关网络协议与法律法规,勿将技术用于任何违规场景。

相关推荐
tachibana21 小时前
Go 语言 Map 的底层实现原理
开发语言·后端·golang·make
程序边界1 小时前
用了三年数据同步软件,我想好好聊聊KFS这套东西到底强在哪(上)
后端
sp421 小时前
简单的 Spring 文件上传与下载组件
后端
量化分析码农1 小时前
【Python量化实盘工程 #04】回测收益虚高30%?滑点与冲击成本建模让模拟更真实
后端
QuantiCore_IO1 小时前
量化回测复权数据的工程实践:从 API 获取到数据质量验证
后端·github·api
程序边界1 小时前
数据同步软件下半场:断了丢不丢、错了能不能修(下)
后端
wei_shuo1 小时前
KFS数据同步软件实战:异构数据同步、实时复制与产品力解析
后端
极派科技1 小时前
凌晨收到告警:MySQL CPU 100%,几千台 IoT 设备同时上报把系统打崩了
后端
王中阳Go1 小时前
做了 8 年前端,简历改漂亮后面一个挂一个:差的不是前端,是这 3 块底子
人工智能·后端·面试