文章目录
- 前言
- 一、概念
- 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 的服务而言,它仅需要密钥的公共部分。
我们在线解密看看:
由于这里使用 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?
当然也没问题,优点更突出,目前主流方案。