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

相關文章
相關標籤/搜索