安卓手机彻底清理微信缓存 文末附对 jwt 、dingo 的一些理解

写在前面

makrdown 原文链接

文档:安卓手机彻底清理微信缓存 文末附对 jw...
链接:http://note.youdao.com/noteshare?id=8e3875c98f38ebfd9942cf2d10b169b8&sub=8F37D9F3AA0141A494DB2652ADD435B7

 

前置场景

简单介绍一下项目场景; PC 网页生成二维码 该二维码为链接跳转 用户手机微信扫一扫跳转到该链接后 执行微信网页授权获取 生成 token,用以接口请求数据

入坑Log

本来项目调试 socket 状态处理 已经是 案牍劳形 ,好不容易各方面觉得问题不大,小伙伴们决定清理一次数据库测试数据重头一遍,好嘛,很快的写好清理数据脚本,数据库里清爽如初恋,同时打开微信通用设置,清除缓存,那跑嘛,大屏生成二维码,扫码,OMG! 为何我没有授权,为何我拉出来就列表数据,用户数据不是我的 ????????? 我丢 死在黎明前的曙光里了嘛??? 'f***k',刷新,清理缓存,再来。。。。。 没有任何改变???? 好嘛,继续排查嘛,抓包,拉接口请求的 token 用这个token获取用户信息,好嘛,不是我的

  • 怀疑写死了token? 多刷几次,token 并没有改变
  • 手机缓存?多次清理,无效 好嘛 想办法

针对安卓手机微信缓存清理

  • 手机微信任意聊天框发送 http://debugtbs.qq.com/ 然后打开

yezUXV.jpg

  • 好的 第一次理论上会羞涩的出现上图提示 那按照步提示来操作
  • 重新发送并打开 debugmm.qq.com/?forcex5=true,等待安装

ymSEHU.jpg

  • 如果出现上图说明执行成功
  • 重新打开 http://debugtbs.qq.com/ 艾玛 真香

ymSYUe.jpg

  • 清除本地缓存 ,重新打开 OK,问题解决

这里是 ending

在写这篇小记的时候,整理了下思路,也想通了问题,下面就来复盘一下

首先我们在正常操作的时候,获取了授权,前端h5页面根据授权接口返回的 token 及 expires_in 做登录态,也就是将 token 缓存,前面提到,我清理了数据库的数据,这个操作后,我也清理了微信缓存,那没为什么没有生效呢。

微信的清理缓存到底是清理了什么???

首先打开微信的帮助与反馈 找到如何清理微信缓存

ymPpZV.jpg

可以看到,只对聊天记录进行了说明,没有说明清理缓存具体操作了什么

好嘛,那去腾讯客服微信产品区找找看

ymPNeP.png

是的,依旧没有

好嘛,那求助一下广大网友,经过 微信开发者社区、百度、csdn、知乎等多方社区浏览,初步总结一下 手机微信通用设置里的缓存清理,只是清理浏览朋友圈产生的图片文字信息,并不会清理网页产生的缓存

好的,这也就说明,我们扫码跳转网页获取用户信息的 token 其实依然存在且是合法的未过期的,这也就是为什么我扫码没有再次授权,

那么为什么能拉出数据,以及用户信息是他人的呢?

简单复习一下 JWT 我使用的框架 是 laravel/framework 使用的JWT 包为 tymon/jwt-auth 下面假设你也用过

首先抓包,拿到请求的 token 取解析一下

ym9H39.png

看到解析出来的结果

{
  "iss": "http://xxx.com/api/weChatAuth",
  "iat": 1612196679,
  "exp": 1612556679,
  "nbf": 1612196679,
  "jti": "4MzMtVTnd20Rq1nn",
  "sub": 5,
  "prv": "23bd5c8949f600adb39e701c400872db7a5976f7",
  "role": "user"
}

先看一下定义


iss (issuer):签发人
sub (subject):主题
aud (audience):受众
exp (expiration time):过期时间
nbf (Not Before):生效时间,在此之前是无效的
iat (Issued At):签发时间
jti (JWT ID):编号


好的, 那从生成 token 看起 简单的操作一下 具体引用及实现过程不做讲述

  $user = User::find(1);
  $token = Auth::guard('api')->fromUser($user);

再从 JWT 包源码看下 fromUser 方法


  /**
     * Alias to generate a token for a given user.
     *
     * @param  \Tymon\JWTAuth\Contracts\JWTSubject  $user
     *
     * @return string
     */
    public function fromUser(JWTSubject $user)
    {
        return $this->fromSubject($user);
    }
    

该方法 接受一个用户模型实例,该实例必须实现 JWTSubject 接口方法 ,我们看一下

接口定义 :


<?php

/*
 * This file is part of jwt-auth.
 *
 * (c) Sean Tymon <tymon148@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tymon\JWTAuth\Contracts;

interface JWTSubject
{
    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier();

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims();
}


用户模型类的实现


    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();  //注意这里
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [
            'role' => 'user'
        ];
    }

我们注意到 getJWTIdentifier 方法 返回的是 $this->getKey() 对于模型来说,这里也就是返回主键 通常为 id 。好的记住这个方法

跟着 formUser 方法我们继续往下走 (下面只展示记录主要方法)


 
    /**
     * Create a Payload instance.
     *
     * @param  \Tymon\JWTAuth\Contracts\JWTSubject  $subject
     *
     * @return \Tymon\JWTAuth\Payload
     */
    public function makePayload(JWTSubject $subject)
    {
        return $this->factory()->customClaims($this->getClaimsArray($subject))->make();
    }

    /**
     * Build the claims array and return it.
     *
     * @param  \Tymon\JWTAuth\Contracts\JWTSubject  $subject
     *
     * @return array
     */
    protected function getClaimsArray(JWTSubject $subject)
    {
        return array_merge(
            $this->getClaimsForSubject($subject),
            $subject->getJWTCustomClaims(), // custom claims from JWTSubject method
            $this->customClaims // custom claims from inline setter
        );
    }

    /**
     * Get the claims associated with a given subject.
     *
     * @param  \Tymon\JWTAuth\Contracts\JWTSubject  $subject
     *
     * @return array
     */
    protected function getClaimsForSubject(JWTSubject $subject)
    {
        return array_merge([
            'sub' => $subject->getJWTIdentifier(),
        ], $this->lockSubject ? ['prv' => $this->hashSubjectModel($subject)] : []);
    }

这里 makePayload 方法接受的就是上面讲到的 实现了 JWTSubject接口的 用户模型

makePayload 方法用于 构建载荷, 载荷中放置了 token 的一些基本信息,以帮助接受它的服务器来理解这个 token。同时还可以包含一些自定义的信息,用户信息交换。

我们看到 getClaimsForSubject 方法,在设置 sub的值的时候,就用到了我之前让大家记住的 User, 模型方法,好吧,我记性不好,那这里我们再来看一下


    /**
    * Get the identifier that will be stored in the subject claim of the JWT.
    *
    * @return mixed
    */
   public function getJWTIdentifier()
   {
       return $this->getKey();  //注意这里
   }


所以我们使用拓展包中 token 载荷里的 sub 值其实就是我们的用户ID,

现在已经是 凌晨 1:56分了,有点打瞌睡了, 构建 token 我们就先了解到这里 QAQ

了解完构建 token 我们再来看一下解析

这里要提到我们使用的 第三个包 dingo/api (下面也假设你使用过)

再项目里通过 token 获取 user 信息 有很多种方法


// 辅助函数
$user = auth()->user();  //其余写法不做描述

// Facade
$user = JWTAuth::parseToken()->authenticate();


再假设你在项目中使用过 dingo的前提下 你还可以直接使用 它提供的 辅助函数来获取


$this->user();

再使用了 JWT 包后,我的理解是,JWT 接管了框架的 Auth 处理(具体源码可以使用Idea 跟踪查看),而 dingo的 配置中


 'auth' => [
        'jwt' => 'Dingo\Api\Auth\Provider\JWT',
    ],

auth验证 (以此项目为例)通常我们配置使用的也是 JWT

所以最终都会走到 JWTAUTH 去处理

那我们看一下JWT 验证获取载荷的方法(具体跟踪不做过多记录)

    /**
     * Authenticate a user via a token.
     *
     * @return \Tymon\JWTAuth\Contracts\JWTSubject|false
     */
    public function authenticate()
    {
        $id = $this->getPayload()->get('sub');

        if (! $this->auth->byId($id)) {
            return false;
        }

        return $this->user();
    }

可以看到 这里就是取的 载荷里的 sub 值作为ID,去候去用户信息,这里记录一下主要的方法

//vendor/tymon/jwt-auth/src/JWTGuard.php

    /**
     * Log the given User into the application.
     *
     * @param  mixed  $id
     *
     * @return bool
     */
    public function onceUsingId($id)
    {
        if ($user = $this->provider->retrieveById($id)) {
            $this->setUser($user);

            return true;
        }

        return false;
    }

    /**
     * Alias for onceUsingId.
     *
     * @param  mixed  $id
     *
     * @return bool
     */
    public function byId($id)
    {
        return $this->onceUsingId($id);
    }

跟着idea 继续追踪 查看 retrieveById 方法的实现

我们发现 jwt 实现了 框架中的Illuminate\Contracts\Auth\UserProvider 接口 而 该接口再框架中的下层实现是

//vendor/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php


    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed  $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier)
    {
        $user = $this->conn->table($this->table)->find($identifier);

        return $this->getGenericUser($user);
    }


好的吧,脑子有点晕哈,信息量有点多

到这里,虽然我能力有限,可能没把源码及相关实现讲的清楚,但我们还是总结一下

设置 token 的时候将 用户 主键标识值(通俗是ID)作为 载荷中 sub 的值 记录,解析 token 获取用户信息的时候 从载荷里获取 sub 值 去查询数据库 获取记录

也即是说,再我没有清除数据库数据的时候,下载的 token 载荷中记录的是 1, 我单方面清理了数据库数据,但这个时候,小伙伴先行授权注册了用户,他的记录值 ID 为1 ,并且用他的信息创建了一些列数据,而我的 token 其实依然是有效的,通过解析拿到的就是我小伙伴的用户记录

当然这是在测试的时候才这样搞,实际生产中我们不会去做这种操作,当然为了解决测试的时候出现这样的问题,我们只需要再清理数据库信息的同时,执行一下


php artisan jwt:secret

重新生成一个加密秘钥,那么用原来客户端缓存的 token 解析就会失败 则从新发起授权即可

来不起了,找个地方睡觉。。。。。。。。

posted @ 2021-02-02 02:54  一刀的刀  阅读(183)  评论(0)    收藏  举报