JWT 简介
以JWT 代替传统Token
JSON Web Token (JWT)是由Auth0所提构出的一个新Token想法,这并不是一套软件、也不是一个技术,如果你在做网站时有用Token验证使用者身份的习惯,那么这个方法你应该很快就能上手。我们先来讲一讲为什么JWT会比传统Token要好。
在传统网站中我们会以Session 来判定使用者是否有登入,由于Session 只会被服务端知道,所以我们就可以Session 中存放一些重要数据并且供之后验证用。
为什么不用Session了?
随着网络的扩展,Session 有个问题,那就是具状态性(Stateful)还有容易受跨网域请求伪造攻击(CSRF Attack)。 先让我们先以具状态性的问题为例。
极具状态性
假设今天有两台服务器通过负载均衡来分派使用者的请求,由于Session是储存在服务端上的,第一次使用者登入时是由Server 1处理,那么这个Session自然也就储存在Server 1 。
但下次负载平衡指派使用者到Server 2 的时候呢? 这个Session也就不存在 ,所以使用者就需要重新登入一次(虽然有办法可解决,但暂不讨论)。
易受跨站请求伪造攻击
由于Session是储存在服务端的,这意味着客户端在发送请求时几乎不用提供什么数据,对吧? 如果今天有人发送了一个删除文章的链接给你,然后你在不知情的情况下就按下去的时候会发生什么事情? 你的文章会这样被删除掉。
为此,有人想出了Token 来解决这个问题,并且能够在多个服务器上跨域使用。
Token解决了什么?
当使用者登入成功时,他会得到一串看起来毫无意义的乱数字串,但这个字串实际上会对应到数据库中使用者的身份,简单说就是另类的帐号,这也被叫做Token。
无状态性
Token本身是不携带数据的且无状态性的(Stateless),当服务器接收到Token时,会主动去数据库中找到使用者的数据表,接着就能够知道这个Token代表着哪个使用者,然后获取相关的数据来使用。
由于多个服务器都是共享一个数据库的,所以我们的服务器现在不会有Session 那样的问题。 但需要注意的是Token 不可以让别人知道,否则别人就能够拥有你的身份、伪造成你。
安全性提高
由于使用者现在必须主动提供 Token,也顺带解决了跨网域请求伪造攻击,如果今天朋友再次给了你删除文章的链接,由于这个链接并不带有 Token,服务器也就不能知道是谁想要删除文章,所以这个动作自然就会变成无效。
为什么要用JWT 取代传统Token?
你能够直接在JWT 中存放数据,而不用额外的查询数据库。
当我们使用传统Token时,我们需要查询数据库并且比对这一个Token是谁的,然后我们才能够取得该使用者的数据。 当有大量使用者涌入时,这可能会让服务器负荷不堪。
而JWT解决了这个问题,因为我们可以直接把使用者数据存放在「Token」中,所以也就省去了额外的数据库开销。 且在微服务架构中也能够方便地获取使用者数据。
到这里你可能会开始想:「 这样不是很危险吗? 」、「 如果使用者自行修改了Token该怎么办? 」,而我们接下来就要讲一下什么是JWT。
什么是JSON Web Token(JWT)?
先看看JWT 的长相吧。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
上面是一个来自官方网站范例 ,我们将以此做为讲解。
JWT 的构成
JWT 实际上是由三个部分组成的。
标头.内容.签名
标头
JWT的标头包含了两个部分,一个是加密类型( alg
),另一个则是定义类型( typ
)。
{ "alg": "HS256", "typ": "JWT" }
而这些内容通过Base64转化就能够得到我们的第一段字串。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
内容
内容是一个自定义的地方,你可以在里面存放使用者的帐号、昵称,这样你就不需要再去数据库查询,这听起来很不安全,但是不用担心。
不过需要注意的是这些数据并没有被「加密」,使用者可以直接看到这些数据,所以不推荐在这里摆放信用卡信息、密码。
{ "sub": "1234567890", "name": "John Doe", "admin": true }
这部分通过Base64转化也就能得到以下字串。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
签名
这就是好玩的地方了,当我们要签发一个JWT 的时候,我们会用一组密码来签名,这就是为了避免有人自己更改内容,然后拼凑一个根本不是我们所产生的JWT 来欺瞒服务端。
这部分的算法是由上方两个区块的Base64以点( .
)符号组合起来,并以一个字符串(在这里是secret
)加密。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), "secret")
然后我们就会得到签名。
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
接着组合起来就是JWT 本体。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
由于secret
这个字符串是储存在服务端的,所以也就没有人能够知道(除非暴力破解)。 任何人都可以修改JWT 的内容,但是当他签发的时候并不知道这个字符串,所以就会有不对的签名,服务端也就自然不会接受这个错误的JWT。
使用JWT 不仅能够节省服务端的数据库连接开销,又能够在数据分享上变得更加便利。