我就先说清弔在哪里:JWT不用存
传统的鉴权:数据库里面放个表,分别记录用户id,cookie,和过期时间,如果要支持多设备同时登录还要加个登录设备列,还要定期清理过期的cookie。用户登录的时候先查表,查到了判断下和传来的cookie是不是一样的。
JWT不同,他就像是带着时间戳的证书,服务端只管签发验证,过期了就过期了反正没存再签发一个就行。登陆的时候就把header和payload再加上服务端上的私钥现场跑一下看看和客户端传上来的能不能对的上。
哈希算法知道吧,什么东西丢进去都有个唯一的值,JWT就是用的这个特性。
JWT长什么样
JWT 是一串字符,用点 . 分成了三部分:A.B.C 可以去这里看看:https://www.jwt.io/

比如这样一个JWT
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30 |
可以看到是三个部分 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 和 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0 和 KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30 中间用英文逗号隔开。
其中部分A:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9就是声明使用的是什么算法,这部分是明文,是可以直接base解码出来的,如图对应的就是下面的内容,指定使用HS256算法
1 | {"alg":"HS256","typ":"JWT"} |

部分B:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0 也是同理,这个也是明文,可以直接base64解码得到附带了过期时间的下面数据
1 | {"sub":"1234567890","name":"John Doe","admin":true,"iat":1516239022} |
然后就是部分C:KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30,这里就是鉴权的关键点。JWT需要服务器上有一个绝对安全的私钥用于签发JWT。可以把它简单想象成类似哈希值的算法,把部分A和部分B加上服务器的私钥(在这个JWT是a-string-secret-at-least-256-bits-long)一起哈希计算得到这么一个特定值。然后就和前面的部分A 部分B拼起来返回客户端。
怎么用?
JWT的签发:登录还是正常传账号密码,但是就不用再弄个cookie表了,直接根据用户id设置一个过期时间加上服务器上的私钥算出一个哈希值传给客户端。
JWT的验证:从请求里拿到用户的JWT,提取其中的部分A和部分B,然后再加上自己私钥,当场算一遍看看能不能和用户传来的JWT的部分C对上。对上那就问题。不用去查数据库。
安全性?客户端不知道服务端的JWT私钥,而又众所周知哈希算法具有不可逆性,服务端可以根据自己的私钥和设置的过期时间签发一个JWT,而客户端不能根据JWT反推服务端私钥,从而无法伪造JWT。无论是延长过期时间,或者修改了登陆的目标用户。部分B就变了,而用户手里却只有部分A和原版部分B加上服务器私钥生成的原版部分C。这样的话修改部分B就会让原版部分C直接失效。
所以,这么验权就不用去查表了,甚至不用把签发的JWT存进数据库里。JWT 的安全性 = Secret Key 的安全性。只要服务器密钥不被黑客偷走,这个机制就是固若金汤的。
吊销JWT:从JWT的设计上来说这个就确实没办法做到吊销或者踢人下线,签发的JWT无法撤销或者收回,所以JWT最好配合redis,鉴权前看看这个JWT有没有在黑名单里面
所以cookie还有用吗
Cookie 即使在 JWT 时代,不仅没有死,反而是最安全的“JWT 容器”。
如果就是简单把JWT代替cookie,后端:直接返回 JSON {“token”: “A.B.C”} -> 前端:收到后,执行 localStorage.setItem(‘token’, ‘A.B.C’) -> 请求:前端代码拦截器取出 Token,手动加在 Header 里:Authorization: Bearer A.B.C。
把JWT存在localStorage里的话,如果站点有XSS漏洞被脚本小子嵌入了一段恶意js并且被浏览器执行的话,<script>把localStorage里的数据发给我</script>
那脚本小子就能拿着JWT登陆了。但是只用一招就能让脚本小子回家种地,就是使用HttpOnly Cookie
生成 JWT 后,不放在 JSON Body 里返回,而是通过 Set-Cookie 头命令浏览器存储。
1 | c.SetCookie("token", jwtString, 3600, "/", "localhost", false, true) |
这样前端 JS 完全不需要也无法操作 Token。浏览器发现请求是发给后端的,自动会把 Cookie 带上。Cookie里面又有JWT。
这个带有JWT的Cookie只能由浏览器在发请求时自动携带,任何JS代码也包括黑客注入的都无法读取它