API 开发中可选择传递 token 接口遇到的一个坑

  1. 在作 API 开发时,不可避免会涉及到登陆验证,我使用的是jwt-auth
  2. 在登陆中会常常遇到一个token过时的问题,在config/jwt.php默认设置中,这个过时时间是一个小时,不过为了安全也能够设置更小一点,我设置了为五分钟。
  3. 五分钟过时,若是就让用户去登陆,这种体验会让用户直接抛弃你的网站,因此这就会使用到刷新token这个功能
  4. 正常状况下是写一个刷新token的接口,当过时的时候前端把过时的token带上请求这个接口换取新的token
  5. 不过为了方便前端也可使用后端刷新返回,直至不可刷新,我用的就是这个方法:使用 Jwt-Auth 实现 API 用户认证以及无痛刷新访问令牌
  6. 而坑就是这样来的,
  • 在必须须要登陆验证的接口设置刷新token
<?php

namespace App\Http\Middleware;

use App\Services\StatusServe;
use Closure;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;

class CheckUserLoginAndRefreshToken extends BaseMiddleware
{
    /**
     * 检查用户登陆,用户正常登陆,若是 token 过时
     * 刷新 token 从响应头返回
     *
     * @param         $request
     * @param Closure $next
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
     * @throws JWTException
     */
    public function handle($request, Closure $next)
    {
        /****************************************
         * 检查token 是否存在
         ****************************************/
        $this->checkForToken($request);

        try {
            /****************************************
             * 尝试经过 tokne 登陆,若是正常,就获取到用户
             * 没法正确的登陆,抛出 token 异常
             ****************************************/
            if ($this->auth->parseToken()->authenticate()) {
                return $next($request);
            }
            throw new UnauthorizedHttpException('jwt-auth', 'User not found');

        } catch (TokenExpiredException $e) {
            try {
                /****************************************
                 * token 过时的异常,尝试刷新 token
                 * 使用 id 一次性登陆以保证这次请求的成功
                 ****************************************/
                $token = $this->auth->refresh();
                $id = $this->auth
                    ->manager()
                    ->getPayloadFactory()
                    ->buildClaimsCollection()
                    ->toPlainArray()['sub'];

                auth()->onceUsingId($id);
            } catch (JWTException $e) {
                /****************************************
                 * 若是捕获到此异常,即表明 refresh 也过时了,
                 * 用户没法刷新令牌,须要从新登陆。
                 ****************************************/
                throw new UnauthorizedHttpException('jwt-auth', $e->getMessage(), null, StatusServe::HTTP_PAYMENT_REQUIRED);
            }
        }

        // 在响应头中返回新的 token
        return $this->setAuthenticationHeader($next($request), $token);
    }
}
复制代码
  • 而有些页面,好比文章列表页面,这个接口登陆与不登陆皆可访问,不过登陆的时候能够在页面上显示是否点赞了这篇文章。因此这个接口直接使用的是jwt-auth默认的option中间件
<?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\Http\Middleware;

use Closure;
use Exception;

class Check extends BaseMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     *
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($this->auth->parser()->setRequest($request)->hasToken()) {
            try {
                $this->auth->parseToken()->authenticate();
            } catch (Exception $e) {


            }

        }

        return $next($request);
    }
}
复制代码
  1. 一开始也没有发现问题,直到测试的时候,发现文章列表页面点赞过的文章,过了一段时间再刷新的时候发现不显示已点赞,可是进入我的中心的已点赞文章能够看到。
  2. 刚开始测试没找出缘由,直接暴力调试代码,发现没获取到登陆用户,一想不对呀,已经传token为什么获取不到。通过发现,去到我的中心,再回到新闻列表页就能够正常显示,过了一段时间又不显示了。
  3. 通过这一轮以后,大概明白,在新闻列表页时,token已通过期,可是当时图方便用的jwt-auth默认的中间件,不会刷新token,因此这个接口获取不到登陆的用户。当进入我的中心,发现当前token已通过期,后台刷新token返回,这时候再回到文章列表页就能够获得正常的数据,一段时间后,token又失效了,因此有没法看到点赞过的文章
  4. 解决方法,本身写一个option中间件,当存在token的时候,也须要作token刷新处理。
<?php

namespace App\Http\Middleware;

use Closure;
use Exception;

class Check extends BaseMiddleware
{

    public function handle($request, Closure $next)
    {
        if ($this->auth->parser()->setRequest($request)->hasToken()) {
            try {
                $this->auth->parseToken()->authenticate();
            } catch (TokenExpiredException $e) {
				// 此处作刷新 token 处理
				// 具体代码能够参考必须须要登陆验证的接口
				// 在响应头中返回新的 token
                return $this->setAuthenticationHeader($next($request), $token);
			} catch (Exception $e) {
			
            }

        }

        return $next($request);
    }
}
复制代码

问题解决。 最后说一个并发会出现的问题:php

# 当前 token_1 过时,先发起 a 请求,以后立刻发起 b 请求
# a 请求到服务器,服务器判断过时,刷新 token_1
# 以后返回 token_2 给 a 请求响应
# 这时候迟一点的 b 请求用的仍是 token_1 
# 服务器已经将此 token_1 加入黑名单,因此 b 请求无效
       token_1         刷新返回 token_2
a 请求 --------> server -------> 成功
       token_1         过时的 token_1,应该使用 token_2
b 请求 --------> server ------> 失败
复制代码

jwt-auth已经想到这种状况,咱们只须要设置一个黑名单宽限时间便可 前端

我设置为 5秒,就是当 token_1过时了,你还能继续使用 token_1操做 5秒时间

原文地址laravel

相关文章
相关标签/搜索