登录的作用是什么?
简单的说,就是让系统确认你是谁,并建立一个“安全的、持续的身份状态”,从而允许你访问属于你的资源。举个例子,当你以游客身份进入淘宝时,你可以浏览商品界面,但是你看不到购物车、无法下单也无法看到订单,因为系统不知道你是谁。
那怎么在登录之后让用户知道你是谁呢?
这时候有同学可能会提出一种简单的思路:在注册之后为每个用户生成 userId,也就是用户的唯一标识,并在登录之后将 userID 存在本地缓存中,在后续每次请求中都将其给带上。然后后端就根据 userId 去查对应用户的数据,并将用户的信息返回给前端
但是,想一想,这样会带来什么问题呢?—— 如果后端直接相信前端传来的 userID 来查数据,那么这个过程中的 userID 是可以被伪造的,问题的关键在于:
> userID 必须来自可信的认证信息,而不是客户端传进来的参数 >
所以,我们必须确报用户的唯一标识是从服务端验证过的信息源中取出,而不是单纯的相信从客户端传入的参数
那么,我们有哪几种方式确保 用户的唯一标识来自可信的认证信息呢
Token
目前,最常用来生成 Token 的方案应该就是 JWT(JSON Web Token)
使用过程
简单来说,在服务端中,登录验证通过后,使用 JWT 的签名算法将用户的关键信息如 ID、用户名进行加密形成 Token,并将 Token 放在响应数据中返回给客户端;而客户端在后续的每次请求都会将此 Token 放在请求头下面的authorization中发给服务端,服务端从对应的字段中接收到 Token 后,会首先调用 JWT 判断 Token 是否被篡改,验证通过后,再将 Token 进行解密,提取出对应的用户信息,再去操作数据库
原理
JWT 的原理可以参考阮一峰的这篇文章:JWT原理,需要核心掌握的就是 JWT 的结构以及 Base64Url 算法(后面会提到)
JWT 分为三个部分
- Header(头部):以 JSON 对象的形式描述 JWT 的元数据,包括签名的算法以及 token 的类型。最后,将上述的 JSON 通过 Base64Url 算法转成字符串
- Payload(负载):用来存放实际需要传递的数据,也通过 Base64Url 算法转为字符串
- Signature(签名):对前两部分的签名,防止数据被篡改
Base64Url 算法:
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法
适用场景
1. 单点登录:因为 token 天然支持跨域、跨系统 2. 微服务架构,分布式
Question
在了解完 token 的原理之后,我们继续深入探索一下几个问题:
1. token 在前端存储在哪里为好:
1. 存储在 cookie 中:cookie 会自动发送给服务端,不过需要在请求发送时添加配置信息;并结合 httpOnly 禁止 js 访问,同时设置 samesite 属性防止 CSRF 攻击
2. 存储在 LocalStorage 中:需要将 Token 信息从本地缓存中取出并添加到请求头下的`authorization`中
2. 怎么实现无感登录的方案呢:参考下述的**双 token 方案**
双 Token 方案
流程
双 token 指的是有两个 token 用来进行身份验证,分别为 access_token 和 refresh_token,正常访问接口时优先带上 access_token,当 access_token 过期后(401),再用 refresh_token 刷新 access_token
为什么需要双 token
保证活跃用户能不过多地重复登录,且提升了非活跃用户的安全性
Question
1. Token 请求队列化 :
场景: 当 JWT 过期时,浏览器会发起一个新的请求来刷新 token。如果此时多个请求同时发起,那么每个请求都会触发一次 token 刷新,从而导致多次请求刷新的问题
解决措施:维护一个 task queue 和 isRefresh 的标识,如果处于正在刷新 token 的阶段,之后发出的多个请求保存在一个队列中,等 token 刷新完后再请求(避免多次刷新 token)
Session
Session 是在用户登录成功后,由服务器自动生成的唯一标识
使用过程
浏览器 ⇄ 服务器
---------- -------------------------
输入账号密码 验证成功 → 生成 Session
↓ ↓
发送 POST /login Set-Cookie: sessionid=xxx
↓ ↓
保存 Cookie 服务器内存保存 sessionid → userInfo
↓
后续请求自动带 Cookie
↓
服务器根据 sessionid 查找用户信息
也就是说,当浏览器接收到服务器返回的 sessionID 之后,会先将此信息存入 cookie,同时 cookie 记录此 sessionID 属于哪个域名;在后续请求的时候,cookie 信息会被自动发送给服务端,服务端也会从 cookie 中获取 sessionID,再根据 ID 寻找响应的 session 信息
适用场景
适合于同源 Web 应用,且无需跨域
Session 和 Token 的区别
| 对比项 | Session | Token |
|---|---|---|
| 存储位置 | 存储在服务端(如内存或 Redis) | 存储在客户端(如 Cookie 或 LocalStorage) |
| 状态管理 | 有状态(需要服务器存储用户会话) | 无状态(不需要服务器存储用户信息) |
| 跨域支持 | 不支持跨域(需要特殊配置或共享存储) | 支持跨域(特别是 JWT) |
| 安全性 | 相对安全,易于控制(服务器存储) | 需要注意防止 XSS 攻击,存储需小心 |