安卓手机彻底清理微信缓存 文末附对 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/然后打开

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

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

- 清除本地缓存 ,重新打开 OK,问题解决
这里是 ending
在写这篇小记的时候,整理了下思路,也想通了问题,下面就来复盘一下
首先我们在正常操作的时候,获取了授权,前端h5页面根据授权接口返回的 token 及 expires_in 做登录态,也就是将 token 缓存,前面提到,我清理了数据库的数据,这个操作后,我也清理了微信缓存,那没为什么没有生效呢。
微信的清理缓存到底是清理了什么???
首先打开微信的帮助与反馈 找到如何清理微信缓存

可以看到,只对聊天记录进行了说明,没有说明清理缓存具体操作了什么
好嘛,那去腾讯客服微信产品区找找看

是的,依旧没有
好嘛,那求助一下广大网友,经过 微信开发者社区、百度、csdn、知乎等多方社区浏览,初步总结一下 手机微信通用设置里的缓存清理,只是清理浏览朋友圈产生的图片文字信息,并不会清理网页产生的缓存
好的,这也就说明,我们扫码跳转网页获取用户信息的 token 其实依然存在且是合法的未过期的,这也就是为什么我扫码没有再次授权,
那么为什么能拉出数据,以及用户信息是他人的呢?
简单复习一下 JWT 我使用的框架 是 laravel/framework 使用的JWT 包为 tymon/jwt-auth 下面假设你也用过
首先抓包,拿到请求的 token 取解析一下

看到解析出来的结果
{
"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 解析就会失败 则从新发起授权即可
来不起了,找个地方睡觉。。。。。。。。

浙公网安备 33010602011771号