JWT —— Json Web Token

前言

JWT,可能很多读者听过,或者了解过,也可能工作的时候用到过。

其全称是Json Web Token,具体的中文翻译,我在网络上也没找到比较好的说法,最多就是把token翻译为较为常用的“令牌”一词。

说到JWT,不得不提session。

由于HTTP请求是无状态的,打个比方,比如你使用同一个电脑,同一个浏览器,打开淘宝,点击将商品放入购物车中,然后点击购物车。

在你的视角中,将商品放入购物车,然后点击购物车,都是“你”的行为,这些操作都是“你”做的。但是,服务器是怎么知道,两次请求是“你”做出来的呢?

那自然需要前端与后端进行配合,识别出这两次操作,是又“你”做出来。如果不识别出来你的身份,那点击购物车查看的时候,说不定会看到别人的购物车。那就乱套了啊!

所以,才会出现session与cookie,以此来解决这个问题。

传统的session认证

我们在服务器,使用session技术,将用户的认证信息存在服务器中,并且在HTTP请求响应时,将其部分信息返回,并告诉浏览器将其保存在cookie中。

下次客户端(或者叫前端)再发送请求时,带上cookie,服务器就能根据其中的信息,找到session中对应保存的用户信息,服务器自然就知道用户是“你”,并且把你的购物车信息返回。

session确实也是很不错的解决办法,但是其存在一定的缺陷:

1. 服务器负载量

session是保存在服务器端的,虽然在小型应用中,用户的信息量不大,并且用户数量页不够多。

但是当应用的规模变大时,用户的信息量变大,用户数量也增加,自然服务器的负载就大了。

服务器程序运行时,大多数的数据都是存储在内存中,更何况是用户信息这种需要快速查找的数据。一旦存储总量变大,内存就会告急,影响服务器程序的运行。

2. 扩展性

如果服务器使用分布式的微服务,那么就要保证分布式服务器中,每个服务器上的用户数据具有一致性,这样就需要相应的负载均衡器,并保证多个服务器上的数据同步。

这样也会增加一定的负担

3. CSRF

CSRF全称为 Cross-site request forgery,翻译为跨站请求伪造。

通俗点说就是网站先诱骗用户登录一些需要认证的网站,获取了对应的cookie后,通过该cookie仿造用户的身份,和服务器进行交互,以此实现攻击。

可以说这是使用session与cookie的一种缺点,但其实际上通过一定的防御措施,是可以避免的,不过在本文就不赘述了,感兴趣的读者可以自己去搜索了解。

总结

session与cookie技术是用来解决认证问题的一种方法,其从提出到现在,虽然有些缺陷或者不足,但经过这么多年的实践以及改进,可以说已经是非常的成熟了。

不过由于我们目前的项目前端是小程序,没有cookie(虽然cookie与JWT并不冲突),再加上学长们之前用的就是JWT的方式,所以我们的项目就用了JWT的方式。

JWT

什么是JWT

前面说了那么多,那么到底什么是JWT呢?

JSON Web Token (JWT) is an Internet proposed standard for creating data with optional signature and/or optional encryption whose payload holds JSON that asserts some number of claims.

—— wikipedia

其是一种互联网提议的标准,用于创建具有可选签名和可选加密的数据,其“有效负载”部分持有Json,并断言了一些声明。

可能读者看完上面的说法, 还是没太搞明白。其通俗地讲,就是一种规范,让服务器按照其规范来进行产生token,并将其返回给客户端。

当客户端再次请求时,携带该token,服务器获取到token后,通过规定好的算法来解密,获取用户信息。

也就是说其与session最大的不同,就是session将用户信息,保存在了服务器,每次客户端请求时,以cookie中的数据作为“索引”,在服务器session中查找对应的信息。

而JWT则是直接将用户的部分信息放入到token中,并通过一定的流程进行加密,避免被人恶意解析。在客户端请求登录后,服务器将token返回,让客户端保存。在后来的请求中,客户端携带上token,服务器从token中直接可以把用户信息取出来。总而言之,就是用户信息不在服务器中保存了,而是保存在这个token中,减轻了服务器存储的压力。

那么很显而易见的一个问题就是,token可以被显而易见的取出来,正如cookie一样。因此我们在生成token时,要使用对称的加密算法,进行加密。既然能被获取到已然是事实了,那我们就只能通过不让别人解析有用信息的方法,防止信息泄漏。

JWT的流程

  • 用户使用用户名密码(或者小程序的openid)来请求服务器
  • 服务器验证用户的信息
  • 服务器通过验证,并发给用户一个token
  • 客户端存储token,在每次请求时,都带上token
  • 服务器验证token并从中取出有用的信息

JWT的构成

JWT包含了三部分:header、payload 以及 signature

JWT

header(头部)

header包含了两部分内容:

  1. 声明类型,这里是jwt
  2. 声明加密的算法,通常直接使用HMAC SHA256

完整的头部是下面这样的

1
2
3
4
{
'alg': 'HS256',
'typ': 'JWT'
}

然后将头部使用base64加密,得到第一部分header:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

payload(负载)

负载就是存放信息的地方,其中包括了:

  1. 标准中注册的声明
  2. 自定义的声明

标准中的声明有(建议但是不强制使用):

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

自定义的声明:

公共声明可以添加任何信息,一般是放一些不太敏感的信息,该部分实际上也是明文。

1
2
3
4
5
6
7
8
{
"iss": "matrix-world.top",
"iat": 1500218077,
"exp": 1500218077,
"aud": "blog.matrix-world.top",
"sub": "1248540980@qq.com",
"user_id": "dc2c4eefe2d141490b6ca612e252f92e"
}

使用base64加密后,得到了第二部分payload:

1
eyJpc3MiOiJtYXRyaXgtd29ybGQudG9wIiwiaWF0IjoxNTAwMjE4MDc3LCJleHAiOjE1MDAyMTgwNzcsImF1ZCI6ImJsb2cubWF0cml4LXdvcmxkLnRvcCIsInN1YiI6IjEyNDg1NDA5ODBAcXEuY29tIiwidXNlcl9pZCI6ImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIn0

signature(签名)

JWT的签名是header与payload在经过base64加密后,拼接在一起,然后使用声明的加密方式,进行加盐secret加密,就构成了签名

1
JED8D7KhTdBBeNqLW4d5N_gQr6IeUfjOPw3n3kXeaNQ

最终将三个部分拼接,并使用 . 进行间隔,就生成了JWT的token

思考

从上面的文字中,可以看出,实际上JWT除了最后的signature,其余部分都可以说是明文了。

我在网络上又找到一篇文章《别再使用JWT作为session系统》,其思路说的也有道理,虽然目前使用JWT比较流行,但是其确实存在一些问题,其作为一次性的授权令牌就是比较好的应用。

同时JWT的思路也值得借鉴,那就是将信息发给用户,让用户保存,在后续请求中带上该信息,即可减轻服务器的存储压力。

我们目前的项目,需要保存用户的一些关键信息,并使用其作为索引,以查找数据库中用户更具体的信息。

直接使用JWT是存在问题的,我们项目的初衷是想隐藏用户id之类的敏感信息,不使其以明文的形式暴露在HTTP请求中,JWT中payload几乎可以说是明文的,不符合要求。

不过可以借鉴JWT的思路,同时再使用对称加密算法,将信息加密,避免了明文传输。

看了不少博客以及文章之后,我们项目最终确定使用加密token来保存信息,并且将信息发送给客户端,让客户端保存。时序图如下:

前后端交互时序图

其他再具体的细节就不方便说了,因为实验室的项目和别人是有合作的,因此就不便透露了。

目前来说,效果还可以,避免用户个别信息明文传输的目标的确是实现了。

参考

  1. 什么是 JWT – JSON WEB TOKEN
  2. (译)别再使用 JWT 作为 Session 系统!问题重重且很危险。
  3. jwt相比普通token,优势在哪?
  4. 维基百科