也说 jwt

最近在一个 Nodejs 群里突然聊到后端怎么授权的问题,引出了 JWT 的使用问题。

  1. JWT 是什么?
  2. JWT 真的不需要在服务器端保存吗?
  3. JWT 和 OAuth2.0 中普通的 token 有什么区别?

我在网上搜索的时候发现下面奇葩的观点

阮一峰:另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。[1]

我也是用过 JWT + OAuth2.0 实现过授权鉴权服务器的,而我的观点和上面的观点是不同的。

我不认同服务器不保存 session id 的做法,要说明原因,我们就需要知道 JWT 是什么。

JWT 是什么

这个问题可以直接看官网的介绍

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

我英文不好,但是大概只能知道 JWT 是一个可以用 JSON 格式来传输数据的 web token,另外这个 token 是有数字加密的,使用 token 是能验证的,这让 token 具有比较好的可信度。

之所以在 token 前面强调 web,官网下面的签名算法就说明了一切:

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

base64UrlEncode这个方法说明了生成的 JWT 是可用在 URL 上的。而且base64UrlEncode是可以 decode 的,这就使得payload中的内容可见。而签名是使用自身前面的headerpayload的 base64 加上一个secrethash 得来的,所以只要这个secret不泄漏,secret的拥有者就能检验数据是否被篡改过。

服务器端是否需要保存 JWT

先说我的答案,需要!

不管怎么样,客户端都是不可信的。服务器很难分辨请求是正常的用户操作还是黑客窃取,作为一个会写爬虫的 nodejs 程序员,深知服务器安全还是需要在服务器端来自己把控的。

不如做个简单的推理,就能很容易反驳阮一峰的说法,如下

如果服务器端不保存session id,利用 JWT 的过期特性和签名特性,服务器在用户登录的情况下签发一个两个小时后过期的 JWT 给浏览器,这个 token 我们先称为 tokenA,但是用户在不到两个小时之内退出,并重新登录,服务器再次给用户签发一个两个小时后过期的 JWT 给浏览器,我们把后签发的称为 tokenB因为 JWT 签发后是不能销毁的,即使是签发者也不能让签证提前过期。而这时候只要 tokenA 还处于有效期内,就会存在两个(甚至多个)同时生效的 token。这是很不安全的。虽然前端会说用户退出登录就在浏览器上销毁 token 了,但是谁能保证用户不会自己在退出登录之前先把 token 取出来呢?后端这是给自己留下安全隐患吗?

综上,在Authorization的应用中使用到 JWT,还是需要保存在服务器端,与之前保存session id的做法一样。

JWT 和普通的 Token 有什么区别

这里的讨论前提是有Authorization的前提下进行的,这里的Authorization意味着用户想的到 token,必须是以账户密码为前提获取的,即使是用 refresh token 换 access token 也是需要先登录。

有人可能觉得这个前提条件可以不设,我想说我就见过知道从 url 上得到的 username 去调用 userinfo 接口就能得到 token 的网站。下面的讨论在这个网站里不成立。

先说说在 JWT 之前,后端做 token 一般都是用用户特征值(唯一 id)加时间戳加后端持有 secret 经过一定的算法生成的,然后放在 redis 里面做鉴权。得到的是一段字符串,前端也看不到授权的权限是什么,只能通过自己请求的接口和接口文档来确定这个 token 的权限访问。

这时候应该有人要发问了

问: JWT 与以前的普通 token 有什么区别?

答:其实也没有什么很大的差别,毕竟 JWT 也只是一种 token,和普通的 token 相比,也只是payload可读,以及能在很大程度上保证payload不被篡改。当然要保证secret足够强大,尽量不是有意义的字符串。

问:token 不可读,这样能增加了安全性,JWT 把数据显式的展示出来,暴露过多的信息,这不是此地无银吗?

答:JWT 的确是显式的把payload的数据展示了,所以一般payload里面只放必要的数据。可能会觉得因为 header 里面声明了签名的 hash 算法,觉得比普通的 token 还容易破解出secret。其实也不必担心,因为 hash 算法不是加密算法,是没有解密一说的。签名只是为了保证payload是否被篡改,并不是对payload加密。

问:那和普通的 OAuth2.0 token 有什么区别,以前的 token 也是这么做的,JWT 就这?

答:把以前的 OAuth2.0 token 用 JWT 封装的,其实还是有一定的好处的,只要在payload里面增加过期时间,持有 JWT 端即可自己判断 token 是否还可用,是否需要重新刷新 token。但是不建议服务器端根据payload中的数据,把其他过多原本应该给签发端发送校验请求的工作转由自己去处理,因为如果这个 JWT 不是用非对称加密签发的,持有端是没办法判断 JWT 是否被篡改。

总结

Authorization不一定需要使用 JWT,以前那种普通的 token 和 JWT 都只是 token,JWT 只是多了一个payload可读,以及签名验证保证payload不被篡改,但是在不使用非对称加密的情况下,持有 JWT 端也没办法判断 JWT 是否被篡改,只能是签发端能判断。

参考 & 引用