我们在之前的文章中简单介绍了JWT,与实现及原理,知道了JSON Web Token(JWT)是一种无状态处理用户身份验证的方式。JWT不会把认证的状态存储到数据库,因此认证用户时也就不需要执行数据库查询。而是基于用户的信息生成Token,客户端在请求中带着Token,这样服务端就可以标识出用户了。

所以一旦Token生成,就一直可以被使用,除非它过期。JWT在生成时有选项,可以设置Token的有效期。

但是如果你想让某个用户的Token失效怎么办呢?比如当用户修改了密码,需要把用户之前登录的所有设备退出已登录状态怎么办?

关于注销

通常客户端会把Token存放在某处,之后再请求中带着去访问需要认证的API。当用户需要退出登录时,一般客户端会把本地存储的Token删除(比如,浏览器存放在local storage)。这样当客户端不带着Token请求时,就会从服务端收到403未认证的响应。

使用过Gmail的用户都知道,有个功能可以管理所有认证过的客户端,可以在一个列表看到之前授权使用的客户端,你可以有选择的把一些不再使用的客户端删除,这样那个客户端就不再可以收发邮件了。

其实就是在服务端把某个客户端的Token删除,这样那个客户端就没办法在访问了。那如果使用JWT怎么做呢?服务端并没有存JWT,所以也不可能把某个JWT失效。

关于过期

JWT是可以指定过期时间的,但是一旦生成,就没有办法修改,使它过期。

你可以在签发JWT时通过exp字段指定Token过期时间。

1
2
3
4
5
6
{
  "sub": "1234567890",
  "name": "Yuan Ping",
  "iat": 1516234022,
  "exp": 1516239022
}

提示:一般每种编程语言都会有JWT的包可用,iatexp一般不需要自己放在payload中。

如果你不想让JWT永久有效,那么就需要设置一个合理的失效时间。一般根据你的应用来决定具体的时间。

比如你想签发一个一天有效的Token,用NodeJS代码举例,如下:

1
2
3
4
5
6
7
const jwt = require('jsonwebtoken');
const payload = {
  "sub": "1234567890",
  "name": "Yuan Ping",
  "iat": 1516234022
}
const token = jwt.sign(payload, 'your-secret', {expiresIn: '1d'})

当Token失效后,客户端再次请求受保护的API时,会得到一个未认证的服务端响应。通常客户端的处理方案是把这个过期的Token删除,重定向到登录页面让用户重新登录。所以上面的示例代码,当1天结束后,客户端会自动让用户退出登录状态。

黑名单方案

前面我们已经说了,一旦Token创建就不能手动修改让它失效。因此,你就不能像控制session一样,在服务端让某个Token失效。

你可能想到可以修改JWT的secret,但是这样会导致所有的用户的Token都失效,并不是某一个Token失效。

有一种实现方案是使用黑名单,把你想立即失效的Token放到这个黑名单中,每次请求时查询这个黑名单,如果当前访问的Token在里面,就返回403未认证的响应。你可能会说,这样黑名单不是会越来越大吗?你可以使用TTL选项进行优化。TTL是指一个记录存亡的时间,比如TTL设置2天,那么2天后这条记录就会自动删除。如果你选择要失效的JWT有效期还有2天,那么加入黑名单时,TTL就设置为2天。Redis是一个很好的选择,这样可以在内存中取到黑名单进行判断。聪明的你一定把这个逻辑放在中间件(middleware)中,而不是在写在每个接口里,对吧?这样只有当Token不在黑名单中,再去走中间件JWT的验证。