In API development, you can choose to pass a hole encountered by token interface

Time:2020-9-28
  1. When doing API development, it will inevitably involve login verification. I use JWT auth
  2. You will often encounter atokenOverdue problems, inconfig/jwt.phpBy default, the expiration time is one hour, but for security, I can set it to five minutes.
  3. After five minutes, if you let users log in, this experience will make users abandon your website directly, so it will use refreshtokenThis function
  4. Normally, a refresh is writtentokenWhen the interface is expired, the front end puts the expiredtokenBring in the request interface in exchange for a new onetoken
  5. However, in order to facilitate the front-end, the back-end can also be used to refresh and return until it cannot be refreshed. I use this method: use JWT auth to implement API user authentication and painlessly refresh access token
  6. And that’s how the pit comes from,
  • Set refresh on the interface that must require login authenticationtoken
<?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
{
    /**
     *Check the user login, the user login normally, if the token expired
     *Refresh token returned from response header
     *
     * @param         $request
     * @param Closure $next
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
     * @throws JWTException
     */
    public function handle($request, Closure $next)
    {
        /****************************************
         *Check if the token exists
         ****************************************/
        $this->checkForToken($request);

        try {
            /****************************************
             *Try to log in through tokne, if normal, get the user
             *Unable to login correctly, throw token exception
             ****************************************/
            if ($this->auth->parseToken()->authenticate()) {
                return $next($request);
            }
            throw new UnauthorizedHttpException('jwt-auth', 'User not found');

        } catch (TokenExpiredException $e) {
            try {
                /****************************************
                 *If the token is expired, try to refresh the token
                 *Log in once with ID to ensure the success of this request
                 ****************************************/
                $token = $this->auth->refresh();
                $id = $this->auth
                    ->manager()
                    ->getPayloadFactory()
                    ->buildClaimsCollection()
                    ->toPlainArray()['sub'];

                auth()->onceUsingId($id);
            } catch (JWTException $e) {
                /****************************************
                 *If this exception is caught, it means that refresh has expired,
                 *The user cannot refresh the token and needs to log in again.
                 ****************************************/
                throw new UnauthorizedHttpException('jwt-auth', $e->getMessage(), null, StatusServe::HTTP_PAYMENT_REQUIRED);
            }
        }

        //Returns a new token in the response header
        return $this->setAuthenticationHeader($next($request), $token);
    }
}
  • Some pages, such as the article list page, can be accessed by logging in or not. However, when you log in, you can display whether you like this article or not. So this interface uses thejwt-authdefaultoptionmiddleware
<?php

/*
 * This file is part of jwt-auth.
 *
 * (c) Sean Tymon <[email protected]>
 *
 * 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. At the beginning, no problem was found. Until the test, we found that the articles liked by the article list page were not displayed when refreshing after a period of time. However, the liked articles entered the personal center can be seen.
  2. At the beginning of the test, we didn’t find out the reason. We just tried to debug the code violently. We found that we didn’t get the login user. It’s wrong. It has been passedtokenWhy not. After discovery, go to the personal center, and then return to the news list page, which can be displayed normally. After a period of time, it will not be displayed.
  3. After this round, I understand that in the news list page,tokenIt’s out of date, but it’s easy to use at that timejwt-authThe default middleware will not be refreshedtokenSo this interface can’t get the login user. When entering the personal center, discover the currenttokenExpired, background refreshtokenReturn. At this time, you can get normal data by returning to the article list page,tokenIt’s invalid again, so I can’t see some articles like it
  4. The solution is to write one yourselfoptionMiddleware, when it existstokenWe need to do ittokenRefresh processing.
<?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) {
                //Refresh token here
                //The specific code can refer to the interface that must be logged in and verified
                //Returns a new token in the response header
                return $this->setAuthenticationHeader($next($request), $token);
            } catch (Exception $e) {
            
            }

        }

        return $next($request);
    }
}

Problem solving.
Finally, there is a concurrent problem

#Current token_ When 1 is expired, request a is initiated first, and then request B is initiated immediately
#A requests to the server. The server judges the expiration date and refreshes the token
#Then return token_ 2 respond to a request
#At this time, the later B request still uses token _1 
#The server has already sent this token_ 1 is added to the blacklist, so the B request is invalid
       token_ Refresh returns token 2
A request -------- > server ------ > successful
       token_ 1 expired token_ 1. Token ﹣ u 2 should be used
B request -------- > server -------- failed

JWT auth has already thought of this situation, we only need to set a blacklist grace time
In API development, you can choose to pass a hole encountered by token interface
I set to5Second, that’s whentoken_1It’s expired. You can still use ittoken_1operation5Second time

Original address