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 协议》,转载必须注明作者和本文链接
本帖由系统于 6年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 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
     *          }
     *      )
     *
     * })
     */
6年前 评论

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

6年前 评论
dividez

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

6年前 评论
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);
    }
6年前 评论
QIN秦同学

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

6年前 评论
QIN秦同学

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

6年前 评论
dividez

file

file

甚至可以这样用

6年前 评论

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

6年前 评论
dividez

使用 jwt 的 中间件

file

6年前 评论

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

6年前 评论
dividez

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

file

file

6年前 评论
dividez

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

6年前 评论

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

5年前 评论