Skip to content

HTTP是无状态的

HTTP 是无状态的。也就是说,HTTP 请求方和响应方间无法维护状态,都是一次性的,它不知道前后的请求都发生了什么。 但有的场景下,我们需要维护状态。最典型的,一个用户登陆微博,发布、关注、评论,都应是在登录后的用户状态下的。 要怎么维持这样的登录状态呢,Cookie 和 Session就是用来做这个滴~


HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。 所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 cookie 或者 session 去实现。

基本概念

cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。从而使服务器知道你曾经来过。Cookie 能够记录并保持诸如用户登录状态、购物车信息等各种状态信息。


cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的靠的是 domain)

Cookie属性

image.pngCookie 的重要属性有:

属性描述
NameCookie 的名称
Value储存在 Cookie 中的字符串值, 必须是字符串
Domain指定 cookie 所属域名,默认是当前域名
Path指定 cookie 在哪个路径(路由)下生效,默认是 '/'
如果设置为 /abc,则只有 /abc 下的路由可以访问到该 cookie,如:/abc/read
Expires/Max-Age这两个属性决定了 Cookie 的过期时间。
Expires 指定了一个具体的过期时间(通常是一个固定的 UTC/GMT 时间)
Max-Age 接收一个以秒为单位的时长,完成和 Expires 同样的任务。如果不设置这两个值,那么 Cookie 则会在关闭浏览器会话时过期
Secure如果设置为 True,意味着 Cookie 只能通过 HTTPS 传输,在HTTP中无效
HttpOnly当此属性为真时,JS 中的 Document.cookie 的 API 无法获取到该 Cookie,可以一定程度上防止 XSS 攻击

Domain**Domain**标识指定了哪些主机可以访问该Cookie的域名。如果设置为.google.com,则所有以google.com结尾的域名都可以访问该Cookie。注意第一个字符为.

响应头写入Cookie HTTP 返回的一个 Set-Cookie 头用于向浏览器写入「一条(且只能是一条)」cookie,格式为 cookie 键值 + 配置键值。

bash
Set-Cookie: username=zxd; domain=zxdfe.com; path=/blog; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly

注意,Cookie的大小只有4k

  • cookie的value如果用于保存用户登录态,应该将该值加密
  • http-only 属性设置了不能通过 JS 访问 Cookie,减少 XSS 攻击
  • Secure 属性设置只能在https请求中携带
  • SameSite 属性规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击

Cookie的有效期

Cookie的有效期可以通过Expires和Max-Age两个属性来设置。

  • Expires即过期时间
  • Max-Age用的是一段时间间隔,单位是秒,从浏览器收到报文开始计算
  • 过期时间如果不设置,或者为负数或0,关闭浏览器即销毁Cookie

Session

基本概念

  • session 是另一种记录服务器和客户端会话状态的机制
  • session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中

image.png

典型的session登录验证流程

image.png

  • 浏览器登录发送账号密码,服务器查数据库,校验用户,
  • 服务端把用户登录状态存为 Session,生成一个 sessionId
  • 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器,响应头中Set-Cookie;
  • 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
  • 之后浏览器访问服务器的时候,SessionId 随 Cookie 带上;
  • 服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找(校验)对应的 Session 信息,
  • 如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。

SessionID 是连接 Cookie 和 Session 的一道桥梁

Session的存储方式

服务端只是给 cookie 一个 sessionId,而 session 的具体内容(可能包含用户信息、session 状态等),要后端自己存一下。 当前主要是将Session存在 Redis(推荐)中, 当然也可以存普通数据库,性能不高

Cookie和Session的区别

Cookie 和 Session 都是为了解决 HTTP 协议无状态性的问题而产生的。详细的区别如下:

  1. 存储位置
  • Cookie 数据存储在客户的浏览器上
  • Session 数据存储在服务器上
  1. 数据安全性
  • Cookie 存储在客户端,数据相对容易被篡改,不是完全安全
  • Session 存储在服务器,安全性较好
  1. 生命周期
  • Cookie 的生命周期由 Expires 或 Max-Age 属性决定,如果没有设置,Cookie 在浏览器关闭时就会失效(此时sessionId也找不到了,会话session)
  • Session 通常有较短的生命周期,用户关闭浏览器或长时间不活动(根据服务器配置)则会过期
  1. 对服务器压力
  • Cookie 存储在客户端,对服务器性能无影响
  • Session 存储在服务器端,如果数据过大或过多,会对服务器性能产生影响
  1. 存储大小
  • Cookie 的大小限制在4KB左右
  • Session 存储在服务器端,如果数据过大或过多,可能会对服务器性能产生影响
  1. 跨域问题
  • Cookie 的跨域需要一定的设置,但实现相对复杂
    • 服务器设置响应头:Access-Control-Allow-Origin / Access-Control-Allow-Credentials
    • 客户端配置:withCredentials : true
  • Session 要实现跨域也需要相应的配置并结合其他手段,如跨域共享 Session,实现也较为复杂

Token

session 的维护给服务端造成很大困扰,我们必须找地方存放它,又要考虑分布式的问题,甚至要单独为了它启用一套 Redis 集群。有没有更好的办法?使用Token(令牌)

token流程

在前后端通信时,Token 可以通过两种常用方式发送给后端:HTTP Header 和 Cookie

随Cookie请求带上Token的方式

image.png

  • 用户登录,服务端校验账号密码,获得用户信息
  • 把用户信息、time、sign等配置编码生成token,通过 cookie set 返回到浏览器
    • 也可以直接通过接口返回,同时浏览器也可以把token存其他地方,比如localStorage
  • 此后用户请求业务接口,通过 cookie 携带 token
    • 更推荐的做法是将Token放到Header中
  • 接口校验 token 有效性,进行正常业务接口处理

把 token 存储在 Cookie 中。服务器通过 Set-Cookie 响应头设置 Cookie,然后在每次请求时,浏览器都会自动带上 Cookie 发送给服务器。

将Token放到请求头中

typescript
fetch('api/user', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})

这种方法的优点是

  1. 可以手动控制何时发送 token,使得前后端交互更灵活。
  2. 同时,因为 token 并不在 Cookie 中,避免了 CSRF 攻击

Refresh Token

业务接口用来鉴权的 token,我们称之为 Access token。越是权限敏感的业务,我们越希望 access token 有效期足够短,以避免被盗用。但过短的有效期会造成 access token 经常过期,过期后怎么办呢? 一种办法是,让用户重新登录获取新 token,显然不够友好,要知道有的 access token 过期时间可能只有几分钟。 另外一种办法是,再来一个 token,一个专门用于生成(刷新) Access token 的 token,我们称为 refresh token。 有了 refresh token 后,请求流程变成这样: b764b256211b4ea182388fd92674fe70~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp

  • Access Token 的有效期比较短,当 Acesss Token 由于过期而失效时,使用 Refresh Token 就可以获取到新的 Token(Access Token),如果 Refresh Token 也失效了,用户就只能重新登录了。
  • 在一种常见的设计策略中,Refresh Token只能使用一次,使用后会被销毁,并产生一个新的Refresh Token一起返回给客户端。这种策略的好处是如果Refresh Token被泄露并被使用,正常用户在使用旧的Refresh Token获取新的Access Token时会发现无法使用,从而可能提早发现Token的泄露。

Token 和 Session的区别

  1. 存放位置:
    • Session:Session 数据存储在服务器端,客户端的请求会带上 Session ID,服务器根据 Session ID 在内存或者数据库中查到保存的状态信息。
    • Token :Token 数据存储在客户端(通常在 Cookie、LocalStorage 或 sessionStorage 中),每次请求会将其送至服务器端验证。
  2. 状态:
    • Session:是有状态的,服务器需要保存每个用户的 Session 数据。
    • Token :是无状态的,服务器不保存用户状态,每次请求都会携带验证信息(Token)。这使得 Token 可以做到跨平台和跨域。
  3. 数据安全:
    • Session:数据存储在服务器端,相对比较安全,但是如果 Session ID 泄露可能会带来安全问题。
    • Token :Token 需要传输的数据可能处于非常开放的环境,容易被截取,需要通过算法进行加解密。
  4. 扩展性:
    • Session:Session 存储在服务器,可能会对服务器资源造成压力,尤其在大规模分布式系统、负载均衡等环境下,处理 Session 变得复杂。
    • Token :由于无状态、分离了服务器和认证,使得系统拓展性更强。

JWT

基本

JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案

  • 是一种认证授权机制
  • JWT 是一种具体的 Token 生成和验证标准

一个 JWT 通常由三部分构成:header(头部), payload(负载)signature(签名)

  1. Header(头部):
    1. 指定了 token 的类型(即 JWT)和所使用的签名算法,如 HMAC、SHA256 或 RSA。
  2. Payload(负载):
    1. 存放了实际需要传递的数据,如用户名、用户角色等。
  3. Signature(签名):
    1. 对 header 和 payload 的数据进行签名,确保数据不被篡改。

image.png JWT 可以用于各种场景,比如:

  • 身份验证:服务器在验证用户登录后,生成 JWT 返回给客户端。以后客户端的每个请求都包含这个 JWT,服务器就可以通过验证 JWT 来确认这个请求是合法的。
  • 信息交换:JWT 可以在各个服务之间安全地传递信息,因为 JWT 是签名的,所以接收方可以验证这个 JWT 是由真正可信的一方发送的。

JWT认证流程

image.pngimage.png

  • 用户输入用户名/密码登录,服务端认证成功后,会返回给客户端一个 JWT
  • 客户端将 token 保存到本地(通常使用 localstorage,也可以使用 cookie)
  • 当用户希望访问一个受保护的路由或者资源的时候,需要请求头的 Authorization 字段中使用Bearer 模式添加 JWT,其内容看起来是下面这样
html
Authorization: Bearer <token>
  • 服务器检测JWT信息,如果合法,则允许用户行为
  • 因为JWT不使用Cookie,所以可以向任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
  • 因为用户的状态不再存储在服务端的内存中,所以这是一种无状态的认证机制

JWT的使用方式

HTTP请求头中

当用户希望访问一个受保护的路由或者资源的时候,可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求头信息的 Authorization 字段里,使用 Bearer 模式添加 JWT。

typescript
GET /calendar/v1/events
Host: api.example.com
Authorization: Bearer <token>

POST请求体中

  • 跨域的时候,可以把 JWT 放在 POST 请求的数据体里。

URL拼接

bash
http://www.example.com/user?token=xxx

常见的前后端鉴权方式

  1. Session-Cookie
  2. Token 验证(包括 JWT,SSO)
  3. OAuth2.0(开放授权)

单点登录

参考:前端鉴权的兄弟们:cookie、session、token、jwt、单点登录 - 掘金

参考

傻傻分不清之 Cookie、Session、Token、JWT - 掘金前端鉴权的兄弟们:cookie、session、token、jwt、单点登录 - 掘金