JWT-Auth 黑名单功能

JWT

如果你使用过 JWT 那么一定会用到 jwt-auth 这个库,其中的黑名单功能你可能并不一定了解。

黑名单

文档中其实提到了 invalidate 方法是用来将 Token 加入黑名单,哪里在用这个功能呢?其实刷新 Token 就是将旧 Token 放入黑名单,然后生成一个新的 Token,看一下代码:

tymon/jwt-auth/src/Manager.php

.
.
.
    public function refresh(Token $token, $forceForever = false, $resetClaims = false)
    {
        $this->setRefreshFlow();

        $claims = $this->buildRefreshClaims($this->decode($token));

        if ($this->blacklistEnabled) {
            // Invalidate old token
            $this->invalidate($token, $forceForever);
        }

        // Return the new token
        return $this->encode(
            $this->payloadFactory->customClaims($claims)->make($resetClaims)
        );
    }
.
.
.

可以看到如果我们开启了黑名单 $this->blacklistEnabled 那么就会调用 invalidate 方法将旧 Token 加入黑名单。 invalidate 方法依次经过了:

  • Tymon\JWTAuth\Blacklistadd 方法;
  • Tymon\JWTAuth\Providers\Storage\Illuminateadd 方法;
  • 最终调用 Laravel 当前的 Cache,将 Token 加入缓存。

有兴趣的同学可以研究一下代码是如何执行的,其实黑名单其实就是将 Token 放入缓存,这样验证这个 Token 的时候,就会去黑名单(缓存)中看看,如果黑名单中有,则验证失败。

同样的,登出(logout)方法也是将 Token 加入黑名单。

tymon/jwt-auth/src/JWTGuard.php

.
.
.
    /**
     * Logout the user, thus invalidating the token.
     *
     * @param  bool  $forceForever
     *
     * @return void
     */
    public function logout($forceForever = false)
    {
        $this->requireToken()->invalidate($forceForever);

        $this->user = null;
        $this->jwt->unsetToken();
    }
.
.
.

使用黑名单

你可能会遇到这种需求:

同一时间只允许登录唯一一台设备。例如设备 A 中用户如果已经登录,那么使用设备 B 登录同一账户,设备 A 就无法继续使用了。

通俗一点解释,假设你有两个手机,一个苹果,一个小米,苹果手机已经登录了你的微信,那么这时使用小米手机登录后,苹果手机中的微信就应该自动退出了。

那么对于接口来说,怎么实现上面这个需求呢?其实我们根本不需要关心具体是哪个设备在登录,我们只需要保证同一个用户,只有最后一次调用接口获取的 JWT 是有效的就行。

我们可以给用户表新增一个字段,或者单独使用一张表,总之是需要先将用户的 Token 存下来,那么下次用户再次登录时,调用如下代码将旧 Token 加入黑名单:

\JWTAuth::setToken($oldToken)->invalidate();

然后将新的 Token 保存下来,最后将 Token 返回。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由 Summer 于 5年前 加精
liyu001989
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 28
Luff

这里会有一个问题,如果这个用户长时间没登录过,Token 已无效。再用这个 oldToken 去加入黑名单会报错。

可以这样写

/* @var \Tymon\JWTAuth\JWTGuard $apiGuard */
$apiGuard = auth('api');
$apiGuard->setToken($oldToken);

 // 检查旧 Token 是否有效
 if ($apiGuard->check()) {
    // 加入黑名单
    $apiGuard->invalidate();
 }
5年前 评论

\JWTAuth::setToken($oldToken)->invalidate();
太方便了 :+1:

5年前 评论

这个功能真是好极了

5年前 评论
Luff

这里会有一个问题,如果这个用户长时间没登录过,Token 已无效。再用这个 oldToken 去加入黑名单会报错。

可以这样写

/* @var \Tymon\JWTAuth\JWTGuard $apiGuard */
$apiGuard = auth('api');
$apiGuard->setToken($oldToken);

 // 检查旧 Token 是否有效
 if ($apiGuard->check()) {
    // 加入黑名单
    $apiGuard->invalidate();
 }
5年前 评论

我在使用登录获取token 报错:
"message": "Type error: Argument 2 passed to Lcobucci\JWT\Signer\Hmac::createHash() must be an instance of Lcobucci\JWT\Signer\Key, null given, called in /home/deploy/erp/releases/7/vendor/lcobucci/jwt/src/Signer/BaseSigner.php on line 34",
这个是啥原因啊?

5年前 评论

@liyu001989 好吧,那我用jwt,本地的可以获取到,一放到线上就报上面的错,

file
报要给实例,,能帮忙看下嘛?

5年前 评论
liyu001989

@小榔头 明白了,jwt-auth:1.0.0-rc.2 使用的是 lcobucci/jwt,检查一下env里面的 JWT_SECRET 配置正确了没

5年前 评论

好文

5年前 评论

我们是保存用户的设备的uuid,每次操作都携带该参数。

5年前 评论

token不需要保存吧,在失效之前获取到当前请求的token
$token = JWTAuth::parseToken()->getToken();
然后再将这个token失效不就好了

5年前 评论
liyu001989

@Gundy 不保存,如何区分,该把某个请求中的 token 加入黑名单呢,你再仔细思考一下。

5年前 评论

@liyu001989 嗯,如果只是退出加入黑名单没毛病,多点登陆是要保存之前的token然后拉入黑名单

5年前 评论

JWT token 不是加密的,可以将 JWT base64解码后的信息,取出时间
用户表记录一下,让之前小于,某个时间的 token 都失效
不过之中的逻辑,比较复杂些。
我理解的 JWT 的好处,在多服务器,可以不用通过主服务器计算。

{"typ":"JWT","alg":"HS256"}
{"iss":"https:\/\/xxx.com\/\/api\/users\/login_mini_app","iat":1533547621,"exp":1534153021,"nbf":1533547621,"jti":"NG0GpSDuGqQSNMOX","sub":4,"prv":"23bd5c8949f600adb39e701c400872db7a5976f7"}
5年前 评论

加入黑名单的是token是存到缓存里的,而且是永不过期的缓存,用户一多的话,缓存会越来越多,而且包括过期的token也是加到缓存里的,都是永不过期的缓存的,这个时间一长,redis里存的东西岂不是要炸了
@liyu001989

5年前 评论
liyu001989

@Insua 默认情况下并不是 forever,看一眼代码

file

forceForever 默认是 false,默认情况下过期时间是 token 的最后可刷新时间。刷新 token 也是一样,所以如果你不主动调用,就没有永不过期的缓存。不会炸,放心使用。

5年前 评论
Insua 4年前

@liyu001989 问下大佬,请问在app上refresh 接口一般什么时候调用

5年前 评论
liyu001989

@jake_zou 当然是 Token 过期了之后刷新用啊

5年前 评论

@liyu001989 那这样是不是很麻烦,每个请求都要加上判断是否过期,过期重新刷新token。
假使现在 api 是 /api/test ,那么只有访问这个 api 等到 response 返回时才知道这个 token 过期了,然后这时去请求刷新 token ,然后再重新 打 /api/test ,不知道是否真的需要这样做

5年前 评论
liyu001989

"那么只有访问这个 api 等到 response 返回时才知道这个 token 过期了"

获取 jwt 的时候,返回了过期时间了,客户端知道什么时候过期

5年前 评论

@QiyueShiyi 过期的token 重新登录的话 check过期的token依旧会报token过期哎 难道又要去刷新?

5年前 评论

@K7 有解决这个问题吗

4年前 评论

该如何检验 token 的正确性呢?token 被设置到了请求的头部,需不需要去检验这个 token 呢?如果需要校验的话,是不是需要先从请求头部解析出 token,再去校验呢?我写了一个方法去校验,不知道是否合理。

file

4年前 评论
liyu001989

@xingxiaoli 肯定要检验啊, jwt-auth 不是提供中间件了吗,laravel 的 auth:api 也行啊

4年前 评论

@liyu001989 嗯呢,我用了jwt-auth的中间件

file

4年前 评论

感觉这个黑名单功能是为了使用jwt 而使用jwt ,不能用redis 直接实现tokens 的过期吗?

4年前 评论
black_wind 3年前

jwt-auth 有没有方法让特定用户的所有 token 都失效?

3年前 评论
xiaochong0302

@darrykinger 其实用了黑白名单就走回了session的老路,还不如直接存user和token的对应关系呢

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!