php jwt token刷新方案,laravel JWT自动刷新 自定义验证器

在前面的文章中,我们引入了JWT的验证方式,但是在前面并没有做过多的处理,只是用JWT生成了一个token,那么今天来就解决后续问题

为了保证用户信息的安全, 我们的生成的JWT不可能一直有效,我们在配置文件里边配置的有有效期,这里的单位是分钟

100b9348283889c1a53242c9673176b2.png

但是当我们用户一直在60分钟之内在操作,但是JWT过期后就需要用户重新登录,这样是不合理的,那么就需要对JWT生成的token进行刷新,从而可以达到用户一直有效请求

那么接下来我们就模拟一下用户的操作,来做个简单的实例

我们先自定义一个路由文件,用来存放我们自己路由

63b6754e796d19e3dd0c1e3c232d38ce.png

然后定义几个路由用来测试

25fde3452cde8e32a50ce7a8d43919ff.png

修改一下JWT失效的时间

86c9e8f25041ae2f67b06b1a5fa47efb.png

在jwt-auth的组件中过期校验是由Expiration中的 validatePayload来校验jwt的过期时间

4fb825bb66e8c649756ad259d52d0c11.png

然后这里进行修改一下,因为过期时间是1分钟不好测试,我们来把这个过期时间进行调整一下,调整为15秒就过期

34140df84cb70ee8490e0a719103cf4f.png

然后我们进行一下测试登录

79b2f82f66692e3d77e0f47719f7a662.png

等15秒后咱们来获取一下用户信息,这里现在会报出token已经过期

de8feaeaa2bfd8c57bc46e98ddd56d1a.png

其实这里存在的问题就已经很明显了,如果用户这个时候在正常的操作,但是我们的token这个时候给过期了,那么对用户的体验来说肯定是不好的,那么下来我们就来解决这个问题

这个时候我们可以在异常这里做一个这样的操作,那就是让他在时间过期后给她重置登录时时间,如果到了这个时间我们需要重置一下请求头的token数据,但这样是手动操作的

82d59c5c260537f79b3561fc5ab99e30.png

然后在刷新页面,就依然可以获取到用户信息,但是这样肯定是不行的,这样做只是为了显示这个效果而已,实际项目这样肯定不可以,token到期后,就直接跳到登录,这样肯定是不可以的

66021f1d928bad91bdb3b47373c4edf5.png

所以我们的token在无刷新界面的情况下就很有必要了

那么代码怎么写?

代码写在哪里呢?

如果说对于token的检测,最好是使用与中间件,因为它可以很方便的帮助由我做到对于api请求的token检测;也就是说代码会写在于中间件中

所以创建一个刷新token的中间件

那么中间件会有什么问题?

1.       它不确定是否用户有登入

2.       它不确定之前登入的用户是否退出了登入

3.       如果当前的token过期了那不就请求就失败了?怎么解决呢?

解决问题以及流程;

1.       可以通过判断token是否存在,如果存在就有登入没有就没有登入

2.       判断用户的登入状态是否为退出;

3.       判断是否token过期

4.       跟新请求头部

流程:

f37f412305c10bf49db44e11fdad6b3c.png

创建自定义JWT自动刷新的中间件

c13cb74005979127e02bf2c44907423d.png

这里是中间件里边的内容,参考地址:https://learnku.com/articles/7264/using-jwt-auth-to-implement-api-user-authentication-and-painless-refresh-access-token

<?phpnamespace App\Http\Middleware;use Auth;use Closure;use Tymon\JWTAuth\Exceptions\JWTException;use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;use Tymon\JWTAuth\Exceptions\TokenExpiredException;use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;class RefreshJwtToken extends BaseMiddleware{public function handle($request, Closure $next){// 检查此次请求中是否带有 token,如果没有则抛出异常。$this->checkForToken($request);// 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException 异常try {// 检测用户的登录状态,如果正常则通过if ($this->auth->parseToken()->authenticate()) {return $next($request);}throw new UnauthorizedHttpException('jwt-auth', '未登录');} catch (TokenExpiredException $exception) {// 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中try {// 刷新用户的 token$token = $this->auth->refresh();echo '
---statr-----
';echo $token;echo '
----end----
';// 使用一次性登录以保证此次请求的成功$sub = $this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub'];auth('api')->onceUsingId($sub);} catch (JWTException $exception) {// 如果捕获到此异常,即代表 refresh 也过期了,// 用户无法刷新令牌,需要重新登录。throw new UnauthorizedHttpException('jwt-auth',$exception->getMessage());}}// 在响应头中返回新的 tokenreturn $this->setAuthenticationHeader($next($request), $token);}}

其实实际上需要的方法就是

$this->auth->parseToken()->authenticate()

校验用户的登入状态

$this->checkForToken($request)

校验是否存在token

setAuthenticationHeader()

向响应输出的时候设置刷新的token

$this->auth->refresh()

刷新token

模仿BaseMiddleware-> authenticate()的写法,不过注意那个方法中所抛出的异常并不是实际所需要的异常JWTException 包含了所有异常对于过期时间的异常也在这里,但是对于目前的情况最好是可以抛出TokenExpiredException 过期的异常;

其实最难的还是后面那个点,这是难点所在。这个点的解释最有从源码的角度解释会好;

$this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub'];

获取jwt-token中的用户id

d36d921369f3c6dba646d2f86d138e6b.png

auth(‘api’)->onceUsingId();将改定的用户设置为登入;其实这一步就是把用户设置成为登录状态

ebb0b7b2dc9fb700e87aed2295b65764.png

然后进行测试

首先需要把上边创建的中间件注册到我们的路由中间件里边去

a6be1f0bca046b3df470d4681c64622d.png

然后修改路由

08b688f428cb258b31adc13267b37d52.png

把Expiration.php恢复,当令牌过期,让其抛出TokenExpiredException异常,让自定义的中间件捕获后在来生成令牌

568a550862a823ece1eee987d0c334ac.png

然后我们在来测试

721c7a84ec54c48a01517f4213c8b3e1.png

间隔一分钟去访问user那个路由,我们的token就已经刷新了

851e10d6bd61a1c2d06b7021bc26e313.png

这边需要注意一点就是token刷新之后就会进入黑名单,之后就不能在进行刷新

所以项目在特殊情况下需要设置这几个参数

b5d1f987b4239e26ddd08431dde022b8.png

1732594a74b0a34a4a70cfeaf722df3f.png

这里需要注意一个问题,中间件谁在前就会执行谁

404a93adcc9efdaa18f22e36d6eca9a7.png

90acc2be5ce29c8e5896ddadd4aaf20a.png

在文章一开始就说了需要判断用户是否登录,由于jwt里边不存在check的检验方法,所以我们自己定义一个webCheck方法

因为在项目中曾经修改过配置文件,把api的认证改为了jwt的认证守卫,

4d18765e71628a55c255f9c0df132fb2.png

这个守卫就位于\vendor\tymon\src

032bd910b1452521479a2995374581e6.png

在之前使用auth(‘api’)-> attempt 的时候实际就是执行的下面的方法。

4997930d06ed86b5d81f90521dc98480.png

直接进入login的方法

1d86ddf6e1dcec0df0963c5b739fdfcc.png

实际上登入的方法参数中并没有太多的操作,就是校验用户的信息,然后再设计登入之后的user

也就是在这个过程中会直接返回出user用户的信息;

而在正常情况下使用auth的时候是可以,因为是使用的web,也就是auth.php中的配置

264ecdbd332fff369d7cf02398073ee4.png

也就是使用的是SessionGurad , 直接进入login方法

ad12a3edcd9aec07455ffb35ca6e480d.png

在通过getAuthIdentifier()方法完成,完成之后然后再设置session缓存;

也就是说用户的信息会缓存在session中

那么来看一下校验;

JWTGurad与SessionGuard的校验,通过查找源码会发现并不存在check方法,这个方法位于Illuminate\Auth\GuardHelpers中;

2b409a5bbcd8b6649841ef3dbd27a351.png

可以看到实际就是使用对应guard(web,api)中的user方法,也就是通过user方法获取用户,如果说用户不存在就会返回false;

bac77dc9c7852adeff12f3b1341e829b.png

827c7f29d0cbbdff21f7147289564272.png

简单解释:

jwt的校验过程:最为重要的点就在于,通过request获取jwt然后再去解析获取用的信息;

session的校验就是从session中获取用户的id然后再去数据库中查询用户

所以这就是问题所以在;那么针对于这个问题的话,既希望能够保持jwt对于api认证,又希望在web端校验(会员登入)

最好的解决办法就是在JWTGurad中添加一个webCheck的校验方法;但是实际上这样也不并不是很好因为版本会更新所以最好的办法就是扩展自定义guard

在项目中通过Auth:: guard (‘守卫名称’) 就可以解析出所需要的守卫;

d35a9ca15239c82e37f70508ec619311.png

然后再通过resolve方法去解析出这个守卫;

1391d32740cb92ed73a0d118376877a6.png

发现这个类中有一个数组customCreators,这说明,我们是可以自定义driver的。也就是说,虽然config/auth.php的guards中说driver只支持session和token,但实际上是可以自己扩展的。

可以测试一下

在这个地方通过dd打印扩展的Guard

8299606e1ccd496c9c82bc0ffecb098e.png

为了方便测试,接下来所有的测试路由均放置于routes/test.php中,并且以test为前缀;并且中间件为web 组

abb4e0b429bb1f0a92e6062737cb41d7.png

然后访问

805420b392960731055ca550d380c8a7.png

那怎么扩展呢,顺藤摸瓜,发现class AuthManager中有一个方法extend:

1cf7d59aecd1da2ec760e4d83b309e33.png

然后看一下是怎么加载JWTGuard的

打开jwt的

db66080b020e15d0c291d3b32806e481.png

这个extendAuthGuard就是在父级AbstractServiceProvider中的方法

d71f32d3181c320ffe0de038dde8f32f.png

可以从方法中就可以看到这就是加载JWTGuard的方法所在;也就是对于auth的guard扩展

其实也就是说JWTGuard 的扩展载入是通过与服务提供LaravelServiceProvider中的boot载入的

接下来自定义一个auth的guard,并且继承与JWTGuard

21f973e5515121aad2b7ff4b13a074ce.png

然后建议这个时候就对于这个Guard进行扩展加入做测试;模仿JWTGuard的载入方式而操作的,

914cfed88b33ade915a74a5683ef56b3.png

然后修改config/auth.php

74f3dc85246e9834f1e857072141daa2.png

在访问

4e09746d119caa37c7c06ea178e11404.png

下来完善自定义的app\Auth\JWTSessionGuard.php的代码

1.       重写JWTGuard控制器

2.       然后加入session的载入 注意RedisFactory 是use Illuminate\Contracts\Session\Session;

3.       下一步在登入成功之后调用updateSession 缓存用户的id

<?phpnamespace App\Auth;use Tymon\JWTAuth\JWT;use Tymon\JWTAuth\JWTGuard;use Illuminate\Http\Request;use Illuminate\Contracts\Session\Session;use Illuminate\Contracts\Auth\UserProvider;use Tymon\JWTAuth\Contracts\JWTSubject;class JWTSessionGuard extends JWTGuard{protected $session;public function __construct(JWT $jwt, UserProvider $provider, Request $request , Session $session){$this->session = $session ;parent::__construct($jwt, $provider, $request);}public function login(JWTSubject $user){$token = $this->jwt->fromUser($user);$this->setToken($token)->setUser($user);$this->updateSession($user->id);return $token;}protected function updateSession($id){$this->session->put($this->getName(), $id);$this->session->migrate(true);}public function getName(){return 'login_jwt_' . sha1(static::class);}}

最后修改AuthServiceProvider中的boot方法

914cfed88b33ade915a74a5683ef56b3.png

然后在测试

ccfdb7b5d4331f8288cb082e498e95cd.png

4c48995b6af3810ba7e085f13bfcda8e.png

然后在测试一下,直接验证成功

6049ae602b7db6b41743d75abd3d0446.png

a122c6a25414d966e4e7d936d9ca9982.png

添加中间件

dba61af5d0fe479becaff1a09462b386.png

af75fbb215a5dd659398ccfa45d55d3d.png

然后还需要引入俩个中间件

6494d3af308c07613b109b6f22a105aa.png

然后在回到咱们用户登录的控制器

aa3729d48abc0557a025f046501f30f8.png

修改路由

0071ae3e96c44a8f74dd26d3ac79fded.png

然后测试,目前就把token本地存储,还有token的刷新就全部弄完了

4df06404d2933a17581f9a98c42df870.png

Java实现JWT token刷新可以通过以下步骤: 1. 在生成JWT token时,将过期时间(exp)设置为一个较短的时间,比如10分钟。 2. 在生成JWT token时,将JWT的唯一标识符(jti)设置为一个随机的字符串,并将其存储在服务端的缓存或数据库中。 3. 当JWT token过期时,客户端需要向服务发送一个请求,请求刷新JWT token。 4. 服务验证客户端请求中的JWT token是否过期,并检查其jti是否存在于缓存或数据库中。如果验证通过,则生成一个新的JWT token,并将其返回给客户端。 5. 客户端收到新的JWT token后,将其保存到本地,并在下一次请求时使用它。 下面是一个使用Java实现JWT token刷新的示例代码: ```java public class JwtUtils { private static final String SECRET_KEY = "your_secret_key"; private static final long EXPIRATION_TIME = 10 * 60 * 1000; // 10 minutes private static final String TOKEN_PREFIX = "Bearer "; public static String generateToken(String username) { String jwt = Jwts.builder() .setId(UUID.randomUUID().toString()) .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); return TOKEN_PREFIX + jwt; } public static String refreshToken(String token) { Claims claims = Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token.replace(TOKEN_PREFIX, "")) .getBody(); String username = claims.getSubject(); String jti = claims.getId(); // check if jti exists in cache or database if (isJtiValid(jti)) { return generateToken(username); } else { throw new JwtException("Invalid token"); } } private static boolean isJtiValid(String jti) { // check if jti exists in cache or database // return true if jti is valid, false otherwise } } ``` 在这个示例代码中,generateToken()方法用于生成JWT token,refreshToken()方法用于刷新JWT token。在刷新JWT token时,我们首先解析出原始token中的username和jti,然后检查jti是否存在于缓存或数据库中。如果jti是有效的,则生成一个新的JWT token并返回给客户端;否则,抛出一个JwtException异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值