什么是 JWT

根据维基百科的定义,JSON WEB TokenJWT,读作 [/dʒɒt/]),是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)。

第一部分头信息指定了该JWT使用的签名算法,HS256 表示使用了 HMAC-SHA256 来生成签名:

header = '{"alg":"HS256","typ":"JWT"}'

第二部分消息体包含了JWT的载荷信息:

payload = '{"loggedInAs":"admin","iat":1422779638}'

第三部分未签名的令牌由base64编码的头信息和消息体拼接而成(使用".“分隔)

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

签名则通过私有的key计算而成,secret是私钥字符串,保管好:

var signature = HMACSHA256(encodedString, 'secret'); 

最后得到token如下:

token = encodedString + '.' + signature

# token看起来像这样: 
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.
gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI 

详细介绍参考:https://www.jianshu.com/p/576dbf44b2ae

go-zero如何鉴权

go-zero默认支持使用JWT认证方式来实现,客户端和服务器端的鉴权。

要实现JWT认证,我们需要分成如下两个步骤

  • 客户端获取JWT token。
  • 服务器对客户端带来的JWT token认证。

具体使用方法参考官方文档:https://github.com/tal-tech/zero-doc/blob/main/doc/jwt.md

JWT的优缺点

先说一种很常见的服务器端Session的方式,通常保存在Redis中。请求中传递的只是token_id。用token的形式来传递状态信息是比较好的跨域访问方式;无论是浏览器,PC端,APP端都可以很好的统一认证。token内保存的信息可以多样。

基于Session和基于jwt的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而jwt是保存在客户端的。

JWT的优点

  • 可扩展性好 应用程序分布式部署的情况下,session需要做多机数据共享,通常可以存在数据库或者redis里面。而jwt不需要。
  • 无状态 jwt不在服务端存储任何状态。RESTful API的原则之一是无状态,发出请求时,总会返回带有参数的响应,不会产生附加影响。用户的认证状态引入这种附加影响,这破坏了这一原则。另外jwt的载荷中可以存储一些常用信息,用于交换信息,有效地使用 JWT,可以降低服务器查询数据库的次数。

JWT的缺点

  • 安全性:由于jwt的payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。

  • 性能:jwt太长。由于是无状态使用JWT,所有的数据都被放到JWT里,如果还要进行一些数据交换,那载荷会更大,经过编码之后导致jwt非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local storage里面。并且用户在系统中的每一次http请求都会把jwt携带在Header里面,http请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用jwt的http请求比使用session的开销大得多。

  • 一次性:无状态是jwt的特点,但也导致了这个问题,jwt是一次性的。想修改里面的内容,就必须签发一个新的jwt。

(1)无法废弃 通过上面jwt的验证机制可以看出来,一旦签发一个jwt,在到期之前就会始终有效,无法中途废弃。例如你在payload中存储了一些信息,当信息需要更新时,则重新签发一个JWT,但是由于旧的JWT还没过期,拿着这个旧的JWT依旧可以登录,那登录后服务端从JWT中拿到的信息就是过时的。为了解决这个问题,我们就需要在服务端部署额外的逻辑,例如设置一个黑名单,一旦签发了新的jwt,那么旧的就加入黑名单(比如存到redis里面),避免被再次使用。

(2)续签 如果你使用jwt做会话管理,传统的cookie续签方案一般都是框架自带的,session有效期30分钟,30分钟内如果有访问,有效期被刷新至30分钟。一样的道理,要改变jwt的有效时间,就要签发新的jwt。最简单的一种方式是每次请求刷新jwt,即每个http请求都返回一个新的jwt。这个方法不仅暴力不优雅,而且每次请求都要做jwt的加密解密,会带来性能问题。另一种方法是在redis中单独为每个jwt设置过期时间,每次访问时刷新jwt的过期时间。

可以看出想要破解jwt一次性的特性,就需要在服务端存储jwt的状态。但是引入 redis 之后,就把无状态的jwt硬生生变成了有状态了,违背了jwt的初衷。而且这个方案和session都差不多了,何必多此一举。

适合使用JWT的场景

  • 有效期短
  • 只希望被使用一次

比如,用户注册后发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几小时之内激活),不能被篡改以激活其他可能的账户,一次性的。这种场景就适合使用jwt。而由于jwt具有一次性的特性。单点登录和会话管理非常不适合用jwt,如果在服务端部署额外的逻辑存储jwt的状态,那还不如使用session。基于session有很多成熟的框架可以开箱即用,但是用jwt还要自己实现逻辑。

总结

每种技术都有其更优的适用场景。对一般的Web应用状态保存来说,我个人更倾向于Redis数据库保存状态信息,而请求时传递SessionID的方式。同时辅以JWT的方式实现更为特别适用的场景。

(完)