尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Cookie、Session、Token一次讲透:登录认证机制与实战选型指南

Cookie、Session、Token一次讲透:登录认证机制与实战选型指南 做Web开发的早晚会遇到这么一关登录状态到底该怎么存翻了一圈资料Cookie、Session、Token各说各话有的说Session安全有的说Token跨域方便还有人说Cookie早就该被淘汰了。尤其刚接触前后端联调或者自己写登录注册功能的时候这三个概念经常搅在一起接口调不通还以为是后端代码写错了其实问题的根源全在认证机制的理解上。我当年也被这东西绕晕过后来在真实项目里跌跌撞撞踩了一圈坑才把这块彻底理清楚。说句大实话这仨不是一个层面的东西它们之间的关系更像是“容器—存储—凭证”的分工协作而不是三个互相对立的方案。这篇文章我不讲什么高深理论就用大白话把它们的来龙去脉、底层逻辑、实际使用场景和真实项目里的坑一次说透保证你看完能马上动手在自己项目里做取舍。1. 三者本质先弄清它们分别解决什么问题1.1 从HTTP的无状态聊起HTTP协议本身是“无状态”的意思是服务器每次收到请求都像个失忆的人它不知道这次请求是不是你上次刚进来的那个人。你登录成功之后刷新一下页面按理说服务器应该记得你是谁但HTTP协议天生没这个记忆能力。这时候就需要有一种机制让服务器“认出你”。你说简单啊那每次请求都带上用户名和密码不就行了行倒是行但每次都要明文传密码既不安全也极其浪费。于是人们就设计了各种更聪明的方案核心目标就一个让你第一次证明过身份之后后面每次请求都能轻量地证明“我就是我”。这就像进健身房第一次办了卡前台把你信息登记了给你一把储物柜钥匙。以后每次来你晃一下钥匙就行不用重新跟人家复述一遍姓名、电话、身份证号。Cookie、Session、Token本质上都是在扮演这把钥匙区别只在于钥匙的样子、存钥匙的地方以及健身房前台记不记得住你。1.2 一个健身房类比看懂三者的协作关系我见过很多初学者上来就搞混Cookie和Session谁包含谁其实完全可以拿健身房这个例子硬套Cookie是你挂在手上的钥匙牌浏览器负责保管每次你走到健身房门口发请求给服务器它会自动把钥匙牌递过去。Session是健身房前台手里的储物柜登记册上面写着“钥匙牌号为A001的人名字叫小明办了年卡”。这个登记册放在服务端服务器每次看到钥匙牌号就去翻登记册。Token是你手机上的电子通行证上面印着“本人小明年卡会员有效期到年底”还带一个防伪签名。健身房大门只需要验一下签名真伪就能直接放你进去前台完全不用翻登记册。所以你说三者是对立关系吗不是。Session几乎总是配合Cookie一起用因为Session本身只负责在服务端存数据它需要一种方式把那个“会话标识”送到浏览器再带回来——Cookie刚好就是最现成的搬运工。Token则更像一张自带信息的通行证可以把Session里的“登记册”部分整个干掉但它的验证环节需要签名算法来支撑。1.3 三个最常被问到的核心差异这三个概念傻傻分不清本质上是因为经常有人在不同的层面进行比较。我帮你把它拆成三个最核心的问题第一个问题状态到底存在哪Cookie存在浏览器本地Session存在服务端Token也大部分存在客户端但Token更特殊——它把“你是谁、你有什么权限”这些信息直接编码在凭证本身里。第二个问题服务器怎么验证你Cookie时代服务器读到Cookie里的内容就能拿到身份信息Session时代服务器必须先拿着会话ID去查存储查到了才知道你是谁Token时代服务器不需要查任何存储直接验签就能确认凭证真假。第三个问题丢失了会怎样Cookie丢了如果里面存了身份信息别人捡到就能冒充Session标识丢了因为服务端登记册还在只要设置合理风险通常可控Token丢了由于服务端不保存状态、无法主动让你这个Token失效危险期会一直延续到它过期为止。理解了这三条后面再看技术细节就不会乱了。2. Cookie细节深入不仅仅是“浏览器小饼干”2.1 Cookie的完整工作机制Cookie是服务器通过HTTP响应头种到浏览器里的一个小型文本片段浏览器拿到之后会按域名、路径、过期时间等规则存起来之后每次向该域名发请求都会自动带上匹配的Cookie。用一个最直观的例子用任何后端语言都只是换个写法本质都是设置这个响应头Set-Cookie: sessionIdabc123xyz; ExpiresWed, 02 Nov 2026 08:00:00 GMT; Path/; HttpOnly; Secure; SameSiteLax浏览器收到这个响应头之后会在本地记录一条Cookie键是sessionId值是abc123xyz域名是当前请求的域名路径限制是/过期时间是2026年11月2日。之后你再去访问该域名下的任何页面请求头里都会带上Cookie: sessionIdabc123xyz服务器从请求头里解析出这个Cookie就知道“哦是abc123xyz这个会话”。2.2 每个属性都要弄明白的工程细节我见过大量Cookie相关bug几乎都是某个属性写错了导致的。这里把几个重要属性逐一拆开讲Expires与Max-AgeExpires指定一个绝对的过期时间点Max-Age指定从设置时刻起相对存活多少秒。现代浏览器以Max-Age优先。如果你两个都不设置那这个Cookie就是会话级Cookie浏览器一关就没了。很多“为什么下次打开还要重新登录”的排查方向就在这里因为你种下的可能是个没设过期时间的会话Cookie。Domain决定Cookie要送到哪个域名。默认情况下只发给设置它的那个域名但如果你设置了Domainexample.com那它的所有子域比如www.example.com、api.example.com都会收到。反过来跨域请求时前端反复跟你抱怨“为什么没有Cookie”八成就是Domain规则没搞对。Path路径限制控制哪些路径下发送。一般设成/最省心但如果你的接口里有些路径不需要带认证信息设了Path之后可以减少无谓的传输。HttpOnly这个属性极其重要。设置之后浏览器禁止JavaScript脚本通过document.cookie去读取这个Cookie。这样即便页面里被注入了恶意脚本攻击者也偷不走你的会话凭证。我强烈建议任何跟身份认证有关的Cookie都必须加HttpOnly这是对抗XSS跨站脚本攻击的第一道防线。Secure要求只在HTTPS连接下才会发送这个Cookie。生产环境必须开不然在Wi-Fi环境下中间人攻击者裸奔在网络上就能抓到你的Cookie。SameSite这是应对CSRF跨站请求伪造的关键武器。Strict表示跨站请求一律不带CookieLax表示跨站场景下只有顶级导航比如你从邮件点链接跳转到该网站才带Cookie一般子资源请求、跨站AJAX都不带None表示任何场景都带但前提是必须同时设置有Secure属性。现在主流浏览器默认SameSite就是Lax这也是为什么很多旧系统升级到新版浏览器之后第三方登录回跳突然拿不到Cookie的原因之一。2.3 Cookie的边界别把什么都往里塞Cookie虽方便但它绝对不是用来装大数据的。浏览器的Cookie容量很小一般单域名Cookie总量也就4KB左右这是浏览器的实际实现约束不是RFC强制规定单个Cookie大小在几千字节以内数量上也有限制。除了体积限制还有个更要命的问题Cookie会在每次请求时自动携带。如果你把一个大对象塞进Cookie那么你的每个请求头都会被撑大不仅浪费带宽某些网关或代理甚至可能因为请求头过大直接给你返回4xx错误。我见过有人把整份用户资料JSON序列化之后塞进Cookie的结果接口频繁报错做了半天排查才发现是Cookie体积超标。正确的做法是Cookie里只放一个不透明的标识符比如会话ID或者Token具体数据存在服务端或者编码在凭证本身里。2.4 为什么不能只在Cookie里存用户名有人会问那我把用户名直接存进Cookie服务器读出来不就知道我是谁了吗何必搞Session和Token这个方案不是完全不行但有两个问题绕不过去。第一你没法校验这个Cookie是不是被篡改过的。你存一个usernameadmin攻击者直接在浏览器里改成usernameroot服务器毫无察觉地就信了。第二你没法控制它失效。用户改密码、管理员封号Cookie还在用户依然能继续访问。真正合理的做法是Cookie里存的是无法被普通人伪造的随机凭证或签名凭证服务器验证的是这个凭证本身而不是直接信任凭证明文里的信息。这也是Session和Token这两个方案存在的根本意义。3. Session机制深挖服务端的“登记册”3.1 Session的工作流程与存储位置Session方案的核心思想是把用户状态放在服务端客户端只保留一个会话ID。完整流程是用户提交用户名密码服务器校验通过后创建一个Session记录并生成一个随机的Session ID。服务器通过Set-Cookie把Session ID发给浏览器。浏览器后续请求自动携带这个Session ID。服务器根据Session ID去Session存储里查找对应的数据比如用户ID、角色权限、过期时间。如果找到且没过期就认为请求已认证继续处理业务。这里Session ID本身通常不带业务含义它就像一把钥匙上的编号号码本身没有意义关键是服务端的“登记册”里对应那条记录。实际项目里Session数据可以存哪从简单到复杂有几种选择内存存储直接存在服务器进程内存里读快但麻烦多——服务一重启就全没了而且多实例部署的时候没法共享。外部缓存最常用的是Redis所有后端实例共用一份Session数据服务重启不影响多机部署也能正常工作。数据库可以存关系型数据库或文档数据库除非有审计和离线分析需求否则一般没必要因为性能问题首选数据库。生产环境我基本只用Redis方案原因很简单Session最需要的就是“快”和“共享”Redis两个都能满足。3.2 Session ID的生成与传输细节Session ID看起来只是一个随机字符串但它的生成质量直接决定安全性。如果生成的ID可以被预测比如按序号递增攻击者就可以通过伪造ID去冒充别人。所以Session ID必须使用足够强的随机源生成比如加密安全的随机数生成器让它不可猜测、不可预测。传输上前面已经说了是借助Cookie。这里有个细节这个Cookie的名字不一定要叫sessionId你可以自定义但要注意的是它必须被设为HttpOnly并且名字别跟业务Cookie撞上。有些框架默认生成的名字可能会跟你的业务Bean字段重复导致解析错乱这种问题排查起来特别隐蔽。3.3 Session的过期策略与“记住我”逻辑Session不会永久有效否则服务端存储会越积越多。常见策略是设置一个过期时间比如30分钟。到期之后用户重新访问就相当于未登录状态。这里有个产品层常见的需求“用户主动关掉浏览器再打开还是要保持登录。”前面说过会话级Cookie会在浏览器关闭时失效。要实现“记住我”的效果在做登录的时候就需要手动把Cookie的过期时间一并拉长——但要注意这不等于让Session永不过期。你必须同时考虑服务端Session的过期时间应该大于或等于Cookie的过期时间否则Cookie还在服务端记录却已经没了用户看到的依然是“登录过期请重新登录”的提示。还要提一下“滑动过期”与“固定过期”的区别。滑动过期意味着只要用户一直有操作每次操作就往后续期固定过期则是不管活动与否到期一律踢下线。金融、政务类系统喜欢固定过期防止有人人走忘记关电脑普通互联网产品大多用滑动过期体验更顺滑。两种模式没有绝对的好坏按业务安全等级选就行。3.4 Session方案的集群困境与解法Session有一个很朴素的问题如果应用部署了多台服务器用户在A服务器登录了Session存在A的内存里下一次请求被负载均衡转发到B服务器B去存储里查不到这个Session用户就被当成未登录了。解法有三个方向按成本从低到高排列Session粘滞Sticky Session让负载均衡把同一个用户的所有请求固定转发到同一台服务器。配置简单但机器故障或发布扩容时还是会出问题。Session共享把Session集中存到Redis之类的独立存储里所有服务器实例都从同一处读写。这是最常用的生产方案既能横向扩容也能平滑发布。无状态化改造压根不用Session改用Token方案让服务器不需要保存任何会话状态。这个彻底解决了共享问题但也带来了Token特有的挑战下一节详聊。4. Token认证深度解析把信任写进凭证里4.1 JWT的结构与签名原理Token这个词含义很广但实际工作中只要说Token绝大多数人默认指JWTJSON Web Token。JWT由三部分组成Header声明类型和签名算法比如{alg:HS256,typ:JWT}。Payload放自定义声明比如用户ID、过期时间、角色等。Signature对前两部分做签名得到的防伪串。三部分各自做Base64编码然后用点号拼起来最终长这样eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c这里必须强调一个原则JWT是签名不是加密。Payload部分做的是Base64编码任何拿到Token的人都能直接解码看到里面的明文内容。所以绝不允许往Payload里塞密码、手机号、身份证号之类的敏感数据。签名的作用是防篡改不是防偷看。签名过程用HS256算法来举例的话验证逻辑就是拿同样的密钥重新把Header和Payload拼起来算一遍HMAC比对算出来的签名和Token自带的签名是否一致。只要密钥不泄露哪怕攻击者把Payload里的role从user改成admin算出来的签名也对不上服务器直接拒收。4.2 Token方案为什么更适合前后端分离和微服务Session时代每个服务都需要访问同一个Session存储才能认出用户。走到微服务架构时问题会变得非常麻烦每个服务都得配置到同一个Redis的连接某次缓存抖动可能影响所有服务。Token方案则完全绕开了这个问题。只要服务端能校验Token签名它就能信任Token里携带的身份信息而不需要去翻存储。这让认证逻辑变成了一个纯粹的计算动作天然适合API网关、微服务、移动端App这类多端、多服务的场景。内部服务之间调用也可以彼此传递同一个Token各自验签完事。还有一个场景是移动端。App不是浏览器没有一个自动管理Cookie的容器虽然理论上也可以手动管理Cookie但姿势极其别扭。Token方案下App拿到Token后存在本地安全存储里每次请求时手动加到Authorization请求头即可这才是移动端的标准玩法。4.3 Token的过期时间与Refresh Token设计Session可以设置服务端过期时间到期不及时续期就失效。Token却有个特性服务端无法主动让它失效。因为服务端根本不保存Token的状态一个还没到期的Token即便用户已经注销甚至被管理员封禁它在剩余有效期内依然可以正常使用。要解决这个问题通常有两种策略第一种策略是设置短生命周期Token。比如让Token只存活15分钟。但15分钟一到用户就被强制要求重新登录这体验太差了。于是引入Refresh Token——一个更长生命周期的凭证比如7天甚至30天专门用来在Access Token过期之后去换一个新的Access Token。用户无感知地续期服务端要“踢人”时只需要让Refresh Token失效下一次换取新Token时就能拦住。第二种策略是在服务端维护一个Token黑名单或Token白名单。黑名单存储“还没到期但已被吊销”的Token白名单则把所有有效Token存下来。这两种做法本质上又引入了服务端状态Token“无状态”的优势就打了折扣但某些安全等级高的业务只能这么做属于典型的“用复杂度换控制权”。我在实际项目里的取舍是普通互联网产品Access Token短命Refresh Token长命就够了金融、后台管理类系统再叠加一个服务端白名单多一道保险。4.4 Token到底放哪里localStorage还是HttpOnly CookieToken方案下还有一个经典争论Token该存在浏览器哪个位置存在localStorage里JavaScript可以自由读取每次请求手动塞进Authorization请求头。好处是跨域场景比较灵活缺点是只要页面被注入恶意脚本Token就能被直接偷走而localStorage对这类攻击完全没有防护。存在HttpOnly Cookie里浏览器自动携带脚本读不到。这解决了XSS窃取问题却引来了CSRF问题——因为Cookie是自动携带的攻击者可以在另一个站点伪造请求让你的浏览器带着Cookie发出危险操作。好在有SameSite属性和CSRF Token机制可以兜底。我在项目里的默认推荐是如果前端是纯静态页加API服务且跨域不是特别复杂的场景优先放HttpOnly Cookie因为它的XSS风险面更小如果业务必须支持完全跨域的API调用再把Token放到安全存储里手动管理。没有绝对安全的方案只有结合自己业务的取舍。5. 三者的横向对比与选型决策说了这么多原理落到选型这一步直接给一张对比表方便你收藏以后查。对比维度Cookie直接存身份信息SessionTokenJWT状态存储位置浏览器服务端凭证本身服务端是否存储状态否是否纯无状态时服务端扩容难度无需处理需要共享存储天然易扩容身份信息篡改难度容易明文不容易依赖服务端不容易依赖签名主动失效能力强删Cookie即可强删Session即可弱到期前无法主动失效跨域支持差受同源策略限制差依赖Cookie好可放请求头移动端适配麻烦麻烦标准做法典型场景简单个人偏好记录传统服务端渲染应用前后端分离、微服务、App选型的时候我一般按这个思路来快速判断做传统服务端渲染的Web系统比如Java Thymeleaf / PHP / 服务端模板直接用Cookie Session模式成本最低、框架支持最好、安全模型也成熟。做前后端分离的单页应用如果业务只在自家域名内跑可以用Session方案也可以把Token放在HttpOnly Cookie里如果接口要给多个域名共用直接上Token方案配合Refresh Token。做纯移动端App或小程序直接用Token方案这是移动端的事实标准。做公共服务平台或微服务架构TokenJWT配合网关统一鉴权几乎是唯一合理的选择。记住一个原则没有绝对先进的方案只有当前场景下是否合适。好几年前有人天天鼓吹“Token已全面取代Session”我在实际项目里看到不少传统系统里的Session依然跑得很健壮反而是强行上Token后因为失效机制没做完善用户被无故踢下线体验还不如原来。6. 实战排查这几个常见坑你迟早会遇到6.1 登录成功了但刷新页面就失效遇到这种问题先按顺序排查三个点第一服务端种下的Cookie有没有设置过期时间。没有Expires也没有Max-Age的话这个Cookie就是会话级Cookie浏览器一关就没了页面刷新可能没事但下次新开Tab就会失效。第二Cookie过期时间与Session过期时间是否匹配。常见做法是Cookie设了30天但Session存储只存活30分钟时间一到服务端记录就没了Cookie还在又有什么意义我建议Cookie死活时间跟Session存储的过期时间对齐要么一起长命要么一起短命。第三Session存储是否因为重启而丢失。如果你用的是内存Session存储每次后端服务重新部署后所有登录用户都会被清掉。生产环境不能这么玩至少得换Redis。6.2 跨域请求怎么都带不上Cookie这是前后端分离项目里最常见的问题。浏览器跨域请求不会自动带上目标域名的Cookie前端和后端需要同时做三件事才能打通前端请求要开启withCredentials在fetch里对应credentials: include后端的CORS响应头必须明确指定允许的Origin不能用*并且设置Access-Control-Allow-Credentials: true后端种Cookie时SameSite属性不能为Strict否则部分跨站场景下根本不会发送。以上三个条件缺一个跨域登录就会失败而且浏览器报错往往只是轻飘飘一句“CORS error”让人不知道到底是哪里挂了。排查时直接打开Network面板看请求头里有没有Cookie以及响应头里的Set-Cookie有没有被浏览器拦截能帮你快速定位问题在哪一端。6.3 Token过期了用户还在页面上提交时才告诉你重新登录这个坑几乎只出现在没做自动续期的Token项目里。Access Token存活期太短用户停留在页面上浏览太久Token悄悄过期了用户毫无感知一提交表单直接被弹回登录页写了半天的内容全没了。这是极其劝退的产品体验。解决方案是分两条腿走第一必要的地方加心跳检测——定时用一个轻量接口验证登录态快过期时主动用Refresh Token换新Token第二在发起业务请求遇到“认证失败”时先尝试用Refresh Token静默续期一次续期成功就重放刚才的请求。这样用户全程无感知体验自然就顺了。还要提醒一句Refresh Token必须要比Access Token更小心保存。它权力更大、寿命更长一旦泄露等于“永久的钥匙”。有条件的话Refresh Token应该做设备绑定防止别人拿你的Refresh Token去别的设备换Token。6.4 明明设置了HttpOnly跨站脚本还是偷到了数据HttpOnly只是保护Cookie不被JS读取但它管不住攻击面更大的API接口。一个典型的攻击链路是攻击者在页面里注入脚本脚本不会去读document.cookie而是直接向服务端发请求拉数据——因为只要浏览器带了有效的认证Cookie这个请求在服务端看来完全合法。所以HttpOnly不等于免疫XSS。该做的防御还是要做所有输入要校验、输出要转义、内容安全策略CSP要配好、持久型XSS要重点防。认证凭证这块再安全输入口被打穿一样防不住。6.5 Cookie体积过大导致请求异常我之前排查过一个诡异的问题接口在测试环境一切正常生产环境偶尔报错而且报错完全没有规律。后来看网络日志才发现一个请求的Header体积已经超过2KB而生产环境的网关对Header总大小有限制超过就拒绝处理。原因就是有人把用户菜单数据序列化塞进了Cookie每帧请求都带着这一大包走自然容易触顶。这种问题排查起来非常费劲因为你自己浏览器里未必复现等到高流量机器上日志刷屏才发现是Header超限。好的实践是Cookie里只放ID数据走接口拉或者只放签名过的Token业务数据一律放服务端。请求头越瘦出问题的概率越低。6.6 同一个浏览器前后登录的账号状态串了多账号切换在同一浏览器下是隐藏高频事故点。常见场景是用户先登录A账号退出后又登录了B账号结果页面显示的却还是A账号的某些信息。原因往往在于退出登录时只删了业务侧的登录态没删干净或者Token的Key使用了固定的字符串不同用户之间产生了缓存覆盖。解决这个问题没有太多花活就是规范好状态生命周期登录时统一写入退出时统一清除多个页面Tab之间尽量用同一套会话状态管理逻辑不要在业务里到处手动改认证数据。你偷懒跳过哪一步线上就会以诡异的方式回报你。用经验说话几个掏心窝的建议唠了大半天理论、实操、坑都聊了最后分享几个我这些年积攒下来的切身体会。第一个建议是别一听“Token更新”就无脑迁移。技术上哪种方案更“先进”不重要重要的是你的业务场景更适合哪种。老系统用Session好好的你没有充分理由就别折腾。第二个是认证这块一定要灰度上线。不管是Session存储从内存换成Redis还是从Session方案切到Token方案都别一把梭。先挑一个低风险模块做小流量验证观察认证错误率、用户反馈、服务端异常日志再逐步扩大范围。我见过太多人一把切过去第二天早晨线上单子满天飞。第三个是写登录认证相关的代码时把日志打到位。认证失败和业务失败不一样它需要看到“是谁在什么时间从哪里请求了什么接口、为什么被拒”。没有充分日志的时候排查认证问题就像闭着眼睛找东西效率极其低下。说回标题那三个词——Cookie、Session、Token它们没有哪个该被踩到泥里也无所谓谁“更好”。它们回答的是同一个问题只是答案的思路不同。你在任何项目里只要想清楚“状态放哪”“怎么验证”这两件事再根据实际场景权衡一下安全、体验、复杂度选型就不会出错。这套思路我用到现在还没遇到过它们摆脱不掉的场景。
返回列表