JWT 在项目中的实际使用

关于JWT 可以参考 JWT 完整使用详解 这里说一下在实际项目中的使用:

laravel 5.5
php7.1
"tymon/jwt-auth": "1.*@rc"

JWT token 的刷新 和 基于 JWT 实现单用户登陆

TOKEN 的刷新

理解

用户登陆后 获取到 tokenA (这个时候 tokenA 的有效期是60分钟)

通过 tokenA 获取到 tokenB (这个时候 tokenB 的有效期是60分钟,tokenA 开始进行 60秒的 倒计时,60秒后就会被拉黑)

tokenB 换取 tokenC (成功后 tokenB 开始进行60秒倒计时,60秒后会被拉黑)

env 配置如下:

JWT_SECRET=jbSn01PbHsFoRzEqHtuOsM3rV3FCsGcI
JWT_BLACKLIST_ENABLED=true  # 是否开启toekn黑名单 生产环境需要开启 宽限时间需要开启黑名单(默认是开启的),黑名单保证过期token不可再用,最好打开
JWT_BLACKLIST_GRACE_PERIOD=60 # 设定宽限时间,单位:秒
JWT_REFRESH_TTL=20160 # 刷新时间  单位:分钟
JWT_TTL=60 # 有效时间  单位:分钟

单用户登陆

基于 JWT token 的单用户登陆, 在 token 的载荷配置中做一点手脚即可:

<?php

namespace App\Http\Controllers;

use Auth;
use App\Business\UserBusiness;
use App\Transformers\UserTransformer;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Facades\JWTFactory;

/**
 * 用户相关
 *
 * @Resource("user", uri="/api")
 */
class UserController extends Controller
{

    protected $userBusiness;

    /**
     * UserController constructor.
     * @param UserBusiness $userBusiness
     */
    public function __construct(UserBusiness $userBusiness)
    {
        $this->userBusiness = $userBusiness;
    }

    /**
     * 用户登陆
     *
     * 使用 `username` 和 `password` 进行登陆。
     *
     * @Post("/login")
     * @Versions({"v1"})
     * @Transaction({
     *      @Request({"username": "foo", "password": "bar"}),
     *      @Response(200, body={"code":1,"time":"2018-08-10 09:32:44","message":"success","data":{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9hcGkuc21hcnR2aWRlby5jb21cL2FwaVwvbG9naW4iLCJpYXQiOjE1MzM4NjQ3NjQsImV4cCI6MTUzMzg2ODM2NCwibmJmIjoxNTMzODY0NzY0LCJqdGkiOiJsZTJObzRLVDZlT0NyVnZCIiwic3ViIjoiZGVkZjYyZTI5MDA0MTFlODgzM2I1NGVlNzVlNTM1MzciLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.OdJlE_pUuttqIxsjKF-FAcZOhMYitS69fh18lPZAYmQ","token_type":"bearer","expires_in":3600}}),
     *      @Response(200, body=
     *          {
     *              "message": "用户不存在",
     *              "code": 4000,
     *              "status_code": 500
     *          }
     *      )
     *
     * })
     */
    public function login(Request $request)
    {
        $rules = [
            'username' => 'required',
            'password' => 'required'
        ];

        $this->_validate($request, $rules);

        $spbill_create_ip = $request->header('x-real-ip')?: $request->ip();

        iLog('----------spbill_create_ip------------'. $spbill_create_ip);

        $username = $request->username;
        $password = $request->password;

        $login_time = time();
        $user = $this->userBusiness->dologin($username,$password,$spbill_create_ip,$login_time);

        // Get the token
        $factory = JWTFactory::customClaims([
            'sub'   => $user->guid,
            'ip' => $spbill_create_ip,
            'login_time' => $login_time
        ]);
        $payload = $factory->make();
        $token = JWTAuth::encode($payload);

        return $this->_response($this->respondWithToken((string)$token));

    }

    protected function respondWithToken($token)
    {
        return [
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => JWTAuth::factory()->getTTL() * 60
        ];
    }
}
<?php

namespace App\Http\Middleware;

use App\Business\ResponseException;
use App\Common\ResponseCode;
use Closure;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;

class ClientCheck extends BaseMiddleware
{
    /**
     * @param $request
     * @param Closure $next
     * @return mixed
     * @throws ResponseException
     */
    public function handle($request, Closure $next)
    {
        $array = $this->auth->payload()->jsonSerialize();

        $user = $this->auth->user();

        if (key_exists('ip',$array) && key_exists('login_time',$array)) {
            if ($array['ip'] != $user->ip || $array['login_time'] != $user->login_at) throw new ResponseException("该账户已在其他设备登陆",ResponseCode::OTHER_CLIENT_LOGIN);
        }
        return $next($request);
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
dividez
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 14

这是什么注释语法

/**
     * 用户登陆
     *
     * 使用 `username` 和 `password` 进行登陆。
     *
     * @Post("/login")
     * @Versions({"v1"})
     * @Transaction({
     *      @Request({"username": "foo", "password": "bar"}),
     *      @Response(200, body={"code":1,"time":"2018-08-10 09:32:44","message":"success","data":{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9hcGkuc21hcnR2aWRlby5jb21cL2FwaVwvbG9naW4iLCJpYXQiOjE1MzM4NjQ3NjQsImV4cCI6MTUzMzg2ODM2NCwibmJmIjoxNTMzODY0NzY0LCJqdGkiOiJsZTJObzRLVDZlT0NyVnZCIiwic3ViIjoiZGVkZjYyZTI5MDA0MTFlODgzM2I1NGVlNzVlNTM1MzciLCJwcnYiOiI4N2UwYWYxZWY5ZmQxNTgxMmZkZWM5NzE1M2ExNGUwYjA0NzU0NmFhIn0.OdJlE_pUuttqIxsjKF-FAcZOhMYitS69fh18lPZAYmQ","token_type":"bearer","expires_in":3600}}),
     *      @Response(200, body=
     *          {
     *              "message": "用户不存在",
     *              "code": 4000,
     *              "status_code": 500
     *          }
     *      )
     *
     * })
     */
5年前 评论

@lovecn 这是一种高级的注释语法

5年前 评论
dividez

@lovecn "dingo/api": "2.0.0-alpha1",

5年前 评论
QIN秦同学

请教关于刷新token, 返回前端处理方法。

在中间件判断token过期后,此时在不影响本次请求情况下,怎么刷新token 返回前端比较好。项目中用到了。不知道如何处理是好?

  • 代码如下
       public function handle($request, Closure $next){
            try {
                    if ($user = $this->auth->parseToken()->authenticate()) {
                        return $next($request);
                    }
                return response()->json(['code'=>402,'data'=>[],'msg'=>'未登录']);
            }catch (\Tymon\JWTAuth\Exceptions\TokenInvalidException $exception) {
                //token 放到黑名单列表了
                return response()->json(['code' => 490,'data'=>[],'msg'=>$exception->getMessage()]);
            }catch (\Tymon\JWTAuth\Exceptions\TokenExpiredException $exception) {
                //token已过期 但是 没放入黑名单前。去刷新token 并返回去。
                try {
                    // 刷新用户的 token
                    $token = $this->auth->refresh();
                    Log::info('过期后的刷新--'.$token);
                    // 使用一次性登录以保证此次请求的成功
                    Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
                    auth()->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
                } catch (JWTException $exception) {
                    // 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。
                    return response()->json(['code'=>407,'data'=>[],'msg'=>$exception->getMessage()]);
                }
            }
            // 在响应头中返回新的 token   这里处理的不太好 
            return $this->setAuthenticationHeader($next($request), $token);
    }
5年前 评论
QIN秦同学

我上面的那个写法,已经解决了刷新token了、 只是postman 用的不是很熟。没看到新返回的token.

5年前 评论
QIN秦同学

是的。就是这样的。谢谢。@dividez

5年前 评论
dividez

file

file

甚至可以这样用

5年前 评论

求解,你的刷新具体的实现

5年前 评论
dividez

使用 jwt 的 中间件

file

5年前 评论

@dividez 用jwt.renew是每次请求都对token 进行了刷新,请问题如何只是在过期的时候才进行刷新,否则就不刷新呢

5年前 评论
dividez

@胸毛仙人 jwt.renew 的刷新, 在 response 中的 token 的创建时间 是不会变的,

file

file

5年前 评论
dividez

@胸毛仙人 每次都刷新了 token , 只要没有过期 旧 token 都可以用, 将 宽限时间 加多一点就好了,个人觉得,用最新的比较好

5年前 评论

@echofree313 能由安政一点的代码吗?

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!