认证F4:cookie、session、token、jwt
| 机制 | 存储位置 | 通信方式 | 生命周期 |
|---|---|---|---|
| Cookie | 浏览器(客户端) | 自动Header携带 | 可设置过期时间 |
| ️ Session | 服务端 | SessionID传递 | 服务端控制 |
| Token | 客户端 | 手动Header添加 | 令牌有效期决定 |
| ️ JWT | 客户端 | Bearer Token | 包含过期时间声明 |
Cookie
Cookies 是某些⽹站为了辨别⽤户身份⽽储存在⽤户本地终端上的数据(通常经过加密)。即,Cookies存放在客户端,⼀般⽤来保存⽤户信息。

Session
HTTP是无状态的,为了能够在HTTP协议上保持住状态,比如用户是否登陆接需要一种方案来把用户的一个个无状态HTTP请求关联起来。这种技术就叫Session。
Session的功能就是个一个个分离的HTTP请求关联起来,只要实现这个功能,基本上本能叫Session的一种实现。
- 在Cookie里放个JSESSIONID,在服务器上保持状态,用户请求来了,根据这个JSESESSIONID去服务器里查状态。这是Tomcat的实现方法。
- 把所有状态都存在Cookie里,服务器给个签名防止伪造,每次请求来了,直接充Cookie里面获取状态,这是JWT的实现方法。
- 在Cookie里放个token,状态不存在中间件里,而是存在Redis里,这也是一种Session实现方法。
- 把Sessin存储在Web中间件中(比如Tomcat),这种做法正在淘汰,因为这种方案对负载均衡不友好,也不利于快速伸缩。
- 把Session存在Redis和前端的才是最佳方案,尤其在微服务架构大行其道的情况下。
只要HTTP还是无状态的,只要保存状态的是刚需,Session就不会消失,变化的只是它的实现方式。

Session和Cookie的关系
-
cookie 是一个实际存在的、具体的东西,http 协议中定义在 header 中的字段。
-
session 是一个抽象概念、开发者为了实现中断和继续等操作,将client和server之间一对一的交互,抽象为“会话”,进而衍生出“会话状态”,也就是 session 的概念。
-
即session描述的是一种通讯会话机制,而cookie只是目前实现这种机制的主流方案里面的一个参与者,它一般是用于保存session ID。
如果没有 Cookie 的话 Session 还能用吗?
一般是通过 Cookie 来保存 SessionID ,假如使用了 Cookie 保存 SessionID 的方案的话,如果客户端禁用了 Cookie,那么 Session 就无法正常工作。
但是,并不是没有 Cookie 之后就不能用 Session 了,比如可以将 SessionID 放在请求的 url 里面https://javaguide.cn/?Session_id=xxx 。这种方案的话可行,但是安全性和用户体验感降低。当然,为了安全也可以对 SessionID 进行一次加密之后再传入后端。
Token

JWT
jwt即json web token,是一种基于 Token 的认证授权机制。jwt本身也是Token,只是使用了规范化的JSON结构的。JWT自身包含了身份验证所需要的所有信息,因此,服务器不需要存储Session信息。
如何使用jwt进行身份验证?
在基于 JWT 进行身份验证的的应用程序中,服务器通过 Payload、Header 和 Secret(密钥)创建 JWT 并将 JWT 发送给客户端。客户端接收到 JWT 之后,会将其保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。
简化后的步骤如下:
- 用户向服务器发送用户名、密码以及验证码用于登陆系统。
- 如果用户用户名、密码以及验证码校验正确的话,服务端会返回已经签名的 Token,也就是 JWT。
- 用户以后每次向后端发请求都在 Header 中带上这个 JWT 。
- 服务端检查 JWT 并从中获取用户相关信息。
两点建议:
- 建议将 JWT 存放在 localStorage 中,放在 Cookie 中会有 CSRF 风险。
- 请求服务端并携带 JWT 的常见做法是将其放在 HTTP Header 的 Authorization 字段中(Authorization: Bearer Token)。
JWT优缺点
优点
1.无状态
JWT自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 JWT 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
2.有效避免了 CSRF 攻击
一般情况下我们使用 JWT 的话,在我们登录成功获得 JWT 之后,一般会选择存放在 localStorage 中。前端的每一个请求后续都会附带上这个 JWT,整个过程压根不会涉及到 Cookie。因此,即使你点击了非法链接发送了请求到服务端,这个非法请求也是不会携带 JWT 的,所以这个请求将是非法的。总结来说就一句话:使用 JWT 进行身份验证不需要依赖 Cookie ,因此可以避免 CSRF 攻击。不过,这样也会存在 XSS 攻击的风险。为了避免 XSS 攻击,你可以选择将 JWT 存储在标记为httpOnly 的 Cookie 中。但是,这样又导致了你必须自己提供 CSRF 保护,因此,实际项目中我们通常也不会这么做。
3.适合移动端应用
使用 Session 进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到 Cookie(需要 Cookie 保存 SessionId),所以不适合移动端。但是,使用 JWT 进行身份认证就不会存在这种问题,因为只要 JWT 可以被客户端存储就能够使用,而且 JWT 还可以跨语言使用。
为什么使用 Session 进行身份认证的话不适合移动端 ?
- 状态管理: Session 基于服务器端的状态管理,而移动端应用通常是无状态的。移动设备的连接可能不稳定或中断,因此难以维护长期的会话状态。如果使用 Session 进行身份认证,移动应用需要频繁地与服务器进行会话维护,增加了网络开销和复杂性;
- 兼容性: 移动端应用通常会面向多个平台,如 iOS、Android 和 Web。每个平台对于 Session 的管理和存储方式可能不同,可能导致跨平台兼容性的问题;
- 安全性: 移动设备通常处于不受信任的网络环境,存在数据泄露和攻击的风险。将敏感的会话信息存储在移动设备上增加了被攻击的潜在风险。
4.单点登录友好
使用 Session 进行身份认证的话,实现单点登录,需要我们把用户的 Session 信息保存在一台电脑上,并且还会遇到常见的 Cookie 跨域的问题。但是,使用 JWT 进行认证的话, JWT 被保存在客户端,不会存在这些问题。
JWT身份认证常见问题及解决办法
1.注销登录等场景下 JWT 还有效
这个问题不存在于 Session 认证方式中,因为在 Session 认证方式中,遇到这种情况的话服务端删除对应的 Session 记录即可。但是,使用 JWT 认证的方式就不好解决了。我们也说过了,JWT 一旦派发出去,如果后端不增加其他逻辑的话,它在失效之前都是有效的。那我们如何解决这个问题呢?查阅了很多资料,我简单总结了下面 4 种方案:
1、将 JWT 存入数据库
将有效的 JWT 存入数据库中,更建议使用内存数据库比如 Redis。如果需要让某个 JWT 失效就直接从 Redis 中删除这个 JWT 即可。但是,这样会导致每次使用 JWT 都要先从 Redis 中查询 JWT 是否存在的步骤,而且违背了 JWT 的无状态原则。
2、黑名单机制
和上面的方式类似,使用内存数据库比如 Redis 维护一个黑名单,如果想让某个 JWT 失效的话就直接将这个 JWT 加入到 黑名单 即可。然后,每次使用 JWT 进行请求的话都会先判断这个 JWT 是否存在于黑名单中。前两种方案的核心在于将有效的 JWT 存储起来或者将指定的 JWT 拉入黑名单。虽然这两种方案都违背了 JWT 的无状态原则,但是一般实际项目中我们通常还是会使用这两种方案。
3、修改密钥 (Secret)
我们为每个用户都创建一个专属密钥,如果我们想让某个 JWT 失效,我们直接修改对应用户的密钥即可。但是,这样相比于前两种引入内存数据库带来了危害更大:
- 如果服务是分布式的,则每次发出新的 JWT 时都必须在多台机器同步密钥。为此,你需要将密钥存储在数据库或其他外部服务中,这样和 Session 认证就没太大区别了。
- 如果用户同时在两个浏览器打开系统,或者在手机端也打开了系统,如果它从一个地方将账号退出,那么其他地方都要重新进行登录,这是不可取的。
4、保持令牌的有效期限短并经常轮换
很简单的一种方式。但是,会导致用户登录状态不会被持久记录,而且需要用户经常登录。
另外,对于修改密码后 JWT 还有效问题的解决还是比较容易的。说一种我觉得比较好的方式:使用用户的密码的哈希值对 JWT 进行签名。因此,如果密码更改,则任何先前的令牌将自动无法验证。
2.JWT 的续签问题
JWT 有效期一般都建议设置的不太长,那么 JWT 过期后如何认证,如何实现动态刷新 JWT,避免用户经常需要重新登录?我们先来看看在 Session 认证中一般的做法:假如 Session 的有效期 30 分钟,如果 30 分钟内用户有访问,就把 Session 有效期延长 30 分钟。JWT 认证的话,我们应该如何解决续签问题呢?
1、类似于 Session 认证中的做法(不推荐)
这种方案满足于大部分场景。假设服务端给的 JWT 有效期设置为 30 分钟,服务端每次进行校验时,如果发现 JWT 的有效期马上快过期了,服务端就重新生成 JWT 给客户端。客户端每次请求都检查新旧 JWT,如果不一致,则更新本地的 JWT。这种做法的问题是仅仅在快过期的时候请求才会更新 JWT ,对客户端不是很友好。
2、每次请求都返回新 JWT(不推荐)
这种方案的的思路很简单,但是,开销会比较大,尤其是在服务端要存储维护 JWT 的情况下。
3、JWT 有效期设置到半夜(不推荐)
这种方案是一种折衷的方案,保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。
4、用户登录返回两个 JWT(推荐)
第一个是 accessJWT ,它的过期时间 JWT 本身的过期时间比如半个小时,另外一个是 refreshJWT 它的过期时间更长一点比如为 1 天。
refreshJWT 只用来获取 accessJWT,不容易被泄露。客户端登录后,将 accessJWT 和 refreshJWT 保存在本地,每次访问将 accessJWT 传给服务端。
服务端校验 accessJWT 的有效性,如果过期的话,就将 refreshJWT 传给服务端。如果有效,服务端就生成新的 accessJWT 给客户端。否则,客户端就重新登录即可。
这种方案的不足是:
- 需要客户端来配合;
- 用户注销的时候需要同时保证两个 JWT 都无效;
- 重新请求获取 JWT 的过程中会有短暂 JWT 不可用的情况(可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT);
- 存在安全问题,只要拿到了未过期的 refreshJWT 就一直可以获取到 accessJWT。不过,由于 refreshJWT 只用来获取 accessJWT,不容易被泄露
4.JWT 体积太大
JWT 结构复杂(Header、Payload 和 Signature),包含了更多额外的信息,还需要进行 Base64Url 编码,这会使得 JWT 体积较大,增加了网络传输的开销。
解决办法:
- 尽量减少 JWT Payload(载荷)中的信息,只保留必要的用户和权限信息。
- 在传输 JWT 之前,使用压缩算法(如 GZIP)对 JWT 进行压缩以减少体积。
- 在某些情况下,使用传统的 Token 可能更合适。传统的 Token 通常只是一个唯一标识符,对应的信息(例如用户 ID、Token 过期时间、权限信息)存储在服务端,通常会通过 Redis 保存。
总结
| 维度 | Cookie | Session | Token | JWT |
|---|---|---|---|---|
| 跨域支持 | ❌ | ❌ | ✅ | ✅ |
| 服务端开销 | 低 | 高 | 中 | 低 |
| 移动端友好 | ★★☆ | ★☆☆ | ★★★☆ | ★★★★ |
| 防御CSRF | ★☆☆ | ★★☆ | ★★★☆ | ★★★★ |
| 分布式支持 | ❌ | 需要改造 | ✅ | ✅ |
| 有效时间控制 | 固定 | 动态 | 固定 | 声明控制 |
| 数据安全性 | 低 | 高 | 中 | 高 |
| 传输效率 | 高 | 中 | 高 | 中 |
| 开发复杂度 | 低 | 中 | 中 | 高 |
| 可扩展性 | 弱 | 一般 | 强 | 极强 |
| 状态 | 有 | 有 | 无 | 无 |
为什么token可以防止csrf攻击?
两种方式:
1.csrftoken,请求时除了携带cookie以外,还需要在请求url或请求体中携带token,服务端校验cookie和token,服务端需要存储token,或者利用某种算法计算得到token。这种方案最好不要把token放在cookie里,所以一般通过请求url或请求体来向服务端传输token。
2.双重cookie验证,这种服务端不需要存储token,而是将token加入到cookie中( csrf一般由第三方网站发起,因此攻击者只能使用cookie而拿不到cookie的值,但是也存在同站的情况,该情况这种方式就无效),客户端拿到cookie和token以后,每次请求除了携带token还需要在header或请求体携带token,服务端拦截请求后先解密出cookie中的token与header或请求体token比对,一致才放行。

浙公网安备 33010602011771号