Cookie、Session、JWT 那些事

文章目录

  • 前言
  • 一、概念
    • 1、Cookie:
    • 2、Session:
    • 3、JWT
  • 二、应用
    • 1. 基本使用
    • 2. 实现 “退出” 功能
  • 总结


前言

目前 C/S 模式盛行,HTTP 是其中最常见的通信协议,我们知道 HTTP 协议是无状态的,但是这场景完全不够用。

比如,我们微信扫描登录一个网站后,肯定不希望频繁登录嘛,那我们的诉求就是希望在一段时间内,网站能够 “记忆” 我们的登录状态,而这个 “记忆” 的能力 HTTP 是没法完成的,所以需要我们额外做点什么 …

你想想,两端通信过程,状态信息要么存在客户端、要么存在服务端,简言之,现在的问题是,要如何存、存哪里的问题。

当然,这块技术已经很成熟了,我们只需要学会怎么去用就 OK 了。

而本文的主角:Cookie、Session、JWT 这些技术就是专门来解决这类问题的。


一、概念

1、Cookie:

前面我们提到,通信状态可以保存在客户端,这里的客户端一般指我们的浏览器,而浏览器就提供了 Cookie 这么一门技术,简单理解就是以 Key / Value 形式存储数据。

当我们向服务器发起请求的时候,就会在 HTTP 请求头上携带上 Cookie 信息,传递到服务端,由服务端进行处理。

什么是 Cookie?

Cookie 是一段不超过 4KB 的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。

安全性?

Cookie 有自身的一些限制,同时也存在一些安全性的问题,比如 不支持跨域、Cookie 容易被窃取 等,所以,你在使用的时候你需要注意!

2、Session:

会话。字如其名,好比你通过微信和朋友聊天,是不是要先进入对话窗口?这个对话窗口,就可以理解成一个简单的会话。

我俩聊天,前前后后说的这些话,一时半会都刻在双方脑子里,不是那种说完就忘了的,会话也是这个理,它有上下文、也是有记忆的、只不过这个记忆就需要客户端或者服务端来 “记” 下来。

既然是对话,肯定涉及两方,因此客户端、服务端一个都不能缺,是吧?

我们再看,通常我们的服务端是中心化的,可以承接千千万万的客户端连接,我们要如何管理这千千万万的连接呢?

在服务端来中心化的管理这些连接,我们也习惯称 会话管理,本质就是管理这些对话状态嘛。

具体怎么用呢?

用户 A 进行了登陆,服务端记录登录状态,并创建一次会话,服务端这边保存了用户的会话数据(用户id、姓名 …),并给这次会话分配全局唯一ID,也称为会话ID(sessionId)。

有效期内,在服务端我们可以通过这个 sessionId 直接定位到其对应的会话状态。

当然,还没完,客户端想要和服务端通信,手里总的拿点凭证吧?于是我们就把这个 sessionId 返回给客户端,客户端怎么存呢?

Cookie,你看,这就串联起来了!

用户登录后,服务端返回 sessionId,然后客户端将 sessionId 存到 Cookie 中,每次请求服务端时,从 Cookie 中取出 sessionId,并放置到请求头中,然后服务端直接从请求头中取出来用就可以了。

3、JWT

所有信息都放在服务端来存储,服务端的压力是不是有点大?能不能将这些压力分担到千千万万的客户端?

答案是可以的,这就是我们目前常用的 JWT 技术。

全称:JSON Web Token,是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

简单说,我们的信息通过特殊方式处理后,就能得到一串相对精简的字符串,也常叫 token,而我们也可以通过这个 token 反解析出具体的信息,这就是 JWT 的处理思路。

有了这种技术之后,在某些场景之下,我们就不需要在服务端中心化的存储这些 会话 状态了,因为这些会话数据都交给了客户端自己保管,每次请求时都会带上。

那服务端怎么验证这个会话呢?前面提到,我们可以直接从 token 解析出用户会话数据,一般就包括用户 id,昵称 等,基本上够用了。

诶,听起来很完美?

别急,凡是有利有弊,服务端不再中心化的记录这些会话状态,最大的问题就是没法管理会话。比如,你想要实现 “退出” 功能,就没法做到了,至于怎么做,我们后面分析。

JWT:

JWT 分为三段,包括头部、有效负载和签名,它们使用 Base64 进行编码后得到一串字符串,也称为 token,我们看个例子:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjdXJyZW50LXVzZXIiOiIwOjI4Ouafj-ayuTEyMyIsIngtb3JpZ2luLXN5c3RlbSI6InBpY2h1IiwiZXhwIjoxNjc3NDM4MDAwfQ.-wtyD7woxpgE9fFXjdcUV_mryuwmXvcFGSZuB4dNdZc
  • 头部:声明了编码对象的类型和密码签名的算法。

  • 有效负载:其中包含与令牌有关声明的详细信息。

  • 签名:防止信息被篡改。签名的格式可以是使用共享密钥的对称算法(HS256)或使用公钥和私钥的不对称算法(RS256)。

对于JWT,最好的选择是非对称选项,因为对于需要认证 JWT 的服务而言,它仅需要密钥的公共部分。

我们在线解密看看:

Cookie、Session、JWT 那些事_第1张图片

由于这里使用 Base64 进行编码,并没有进行不可逆的加密,直接进行解码就能得到明文,所以,要避免存放重要信息,防止泄漏。当然,你也可以将重要的信息加密后存放。

JWT 的优点:

在于支持跨语言,JSON 格式提供了语言无关性;同时,Token 占用字节小,便于传输。

JWT 的缺点:

Token 的注销方式。由于 Token 不存储在服务端,当用户注销时,Token 的有效时间可能还没有到,所以如何在用户注销的同时让 Token 失效是实现上的难点。

二、应用

鉴于目前 JWT 的流行,以及在工作中使用的较多,我们就以 JWT 来看看实战效果。

1. 基本使用

当用户登录时,根据用户信息使用 JWT 生成一个 token,然后返回给前端,前端每次向服务端发起请求的时候都带上 token。

服务端接收到请求后,一般是网关来验证 token 的合法性(一般就是简单的反解析)、并从 token 中获取到用户的信息进行使用。

生成 token:

val token = JWT.create()
        .withExpiresAt(Date(System.currentTimeMillis() + expire))
        .withClaim(CURRENT_USER, "${user.id}:$name")
        .sign(Algorithm.HMAC256(properties.jwtSecret))

你可以看到,我们这里定义了 token 过期时间、必要的用户信息、以及选择的签名算法。

解析 token:


   ...

  try {

   ...

   final List<String> authorization = request.getHeaders().get("authorization");
   final DecodedJWT jwt = JWT.require(algorithm).build().verify(authorization.get(0));
   String user = jwt.getClaim(CURRENT_USER).asString();
   String userId = user.split(":")[0];

  } catch (JWTDecodeException | SignatureVerificationException te) {
      throw new JWTException(401, null, "会话已过期");
  }  catch (TokenExpiredException e) {
      // 过期token不做处理,由下游拦截
      log.warn("[Token Filter] 失效token [{}]", authorization);
  }

   ...

从请求头里取出存放 token 的 authorization 字段,再使用 JWT 进行反编码即可,当 token 无效或者过期会直接抛出异常,我们用 try … catch … 捕获之后按需处理即可。

其实,就这两个简单的步骤,我们就完成了使用 JWT 实现基本的会话状态保存了能力。

2. 实现 “退出” 功能

前面说过,由于 JWT 是非中心化的管理方式,是没法直接做到这个功能的,不过我们可以找点 “野路子”。

分场景来看:

1)一般的后台系统:

如果选择退出登录时,可以让前端直接清理掉浏览器的缓存,这样再去请求接口时,由于请求头没有携带 token,后端会直接返回重定向到登录页面,然后引导用户登录就行了。

那以前的 token 是不是也可以直接用?

是的,所以后端还是要做好 token 有效期的控制,比如 一天、甚至直接让它凌晨某个时间点统一过期点,第二天在强制进行登录即可。

可以看到,这种场景下,我们直接使用 JWT 就可以完成了。

2)C 端:

对 C 端用户这块一般要求就要高点了,这个时候在服务端中心化的维护会话状态就非常有必要了。

我们可以在服务端生成 token 后,顺便在 redis 记录一下 token 的过期时间,然后每次请求时,验证下过期时间。

这种方式和传统的 session 方式很类似?

每次,处理流程上都差不多,但还是有优点:我们这里只记录了 token 过期时间,并没有记录更多的用户信息,所以,相对来说占用的存储空间更小。

在哪里验证 token 有效期?

还是在网关,这是所有请求的入口,方便全局控制。

Cookie 和 Token 有点像?

本质都是 K/V 存储关键信息,token 需要存到浏览器的 localStorage 中,而 Cookie 由浏览器存储到小文本中。

Cookie 是不允许跨域访问的,token 则不存在这个问题。

总结

直接选择 Cookie + Session 这套方案?

可以的,也是非常盛行、经得起时间考验的方案。

选用 JWT?

当然也没问题,优点更突出,目前主流方案。

你可能感兴趣的:(JAVA,服务器,java,前端)