欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

JWT 完整使用详解 有大用 有大大用 有大大大用

JWT 完整使用详解

2018/5/7

  • 说实话,官方文档也是相当的乱,这次根据文档并查看源码实验了很多地方,大改了一次。

2018/10/4

  • 结合大家提出的问题和我近期新的理解,写了一篇新文章,讲的比较深,大家可以去看看 JWT 超详细分析。本文这次主要修改了一些细节,对大家提出的问题整理写到文章中。

本文是以 1.0.0-rc 为基准的。

JWT 全称 JSON Web Tokens ,是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。它的两大使用场景是:认证和数据交换。

一、安装之前

资料

先摆出几个参考资料,可以把连接都打开,方便查阅:

二、安装及基础配置

Laravel

1. 使用 composer 安装

# 建议使用1.0以上版本
composer require tymon/jwt-auth 1.*@rc

2. 进行一些配置

这里指的注意的是,有些文档会说要添加 Tymon\JWTAuth\Providers\LaravelServiceProvider::class ,这只在 Laravel 5.4 及以下版本是必要的,更新的 Laravel 版本无需添加。

还有一些文档说要添加 Tymon\JWTAuth\Providers\JWTAuthServiceProvider 这是很久以前的 JWT 版本的(大概 0.5.3 以前的版本)。

2.1 发布配置文件

# 这条命令会在 config 下增加一个 jwt.php 的配置文件
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

2.2 生成加密密钥

# 这条命令会在 .env 文件下生成一个加密密钥,如:JWT_SECRET=foobar
php artisan jwt:secret

2.3 更新你的模型

如果你使用默认的 User 表来生成 token,你需要在该模型下增加一段代码

<?php

namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements JWTSubject    # 这里别忘了加
{
    use Notifiable;

    // Rest omitted for brevity

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}

2.4 注册两个 Facade

这两个 Facade 并不是必须的,但是使用它们会给你的代码编写带来一点便利。

config/app.php

'aliases' => [
        ...
        // 添加以下两行
        'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth',
        'JWTFactory' => 'Tymon\JWTAuth\Facades\JWTFactory',
],

如果你不使用这两个 Facade,你可以使用辅助函数 auth ()

auth () 是一个辅助函数,返回一个 guard,暂时可以看成 Auth Facade。

对于它有很多有必要说的,可以看我单独写的一篇文章 ——Laravel 辅助函数 auth 与 JWT 扩展详解

// 如果你不用 Facade,你可以这么写
auth('api')->refresh();
// 用 JWTAuth Facade
JWTAuth::parseToken()->refresh();

两个 Facede 常用可使用方法,可以看文章后面的附录。

2.5 修改 auth.php

config/auth.php

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'jwt',      // 原来是 token 改成jwt
        'provider' => 'users',
    ],
],

2.6 注册一些路由

注意:在 Laravel 下,route/api.php 中的路由默认都有前缀 api 。

Route::group([

    'prefix' => 'auth'

], function ($router) {

    Route::post('login', 'AuthController@login');
    Route::post('logout', 'AuthController@logout');
    Route::post('refresh', 'AuthController@refresh');
    Route::post('me', 'AuthController@me');

});

2.7 创建 token 控制器

php artisan make:controller AuthController

AuthController

值得注意的是 Laravel 这要用 auth('api') ,至于为什么,我另一篇关于 JWT 扩展详解的文章里有讲。

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class AuthController extends Controller
{
    /**
     * Create a new AuthController instance.
     * 要求附带email和password(数据来源users表)
     * 
     * @return void
     */
    public function __construct()
    {
        // 这里额外注意了:官方文档样例中只除外了『login』
        // 这样的结果是,token 只能在有效期以内进行刷新,过期无法刷新
        // 如果把 refresh 也放进去,token 即使过期但仍在刷新期以内也可刷新
        // 不过刷新一次作废
        $this->middleware('auth:api', ['except' => ['login']]);
        // 另外关于上面的中间件,官方文档写的是『auth:api』
        // 但是我推荐用 『jwt.auth』,效果是一样的,但是有更加丰富的报错信息返回
    }

    /**
     * Get a JWT via given credentials.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login()
    {
        $credentials = request(['email', 'password']);

        if (! $token = auth('api')->attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    /**
     * Get the authenticated User.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function me()
    {
        return response()->json(auth('api')->user());
    }

    /**
     * Log the user out (Invalidate the token).
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        auth('api')->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    /**
     * Refresh a token.
     * 刷新token,如果开启黑名单,以前的token便会失效。
     * 值得注意的是用上面的getToken再获取一次Token并不算做刷新,两次获得的Token是并行的,即两个都可用。
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        return $this->respondWithToken(auth('api')->refresh());
    }

    /**
     * Get the token array structure.
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60
        ]);
    }
}

Lumen

1. 使用 composer 安装

上面是用命令行安装的,这里用 composer.json 安装。

// 我当时可用的版本是这个
"tymon/jwt-auth": "1.*@rc"

执行

composer update

2. 进行一些配置

2.1 开启 Facade 和 Eloquent

取消以下行的注释。

bootstrap/app.php

// $app->withFacades();

// $app->withEloquent();

2.2 开启中间件认证

取消以下行的注释。

bootstrap/app.php

// $app->routeMiddleware([
//     'auth' => App\Http\Middleware\Authenticate::class,
// ]);

// $app->register(App\Providers\AuthServiceProvider::class);

2.3 添加服务提供者

bootstrap/app.php

// 有些文档里是说添加 Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,那是旧版本的
$app->register(\Tymon\JWTAuth\Providers\LumenServiceProvider::class);

2.4 生成加密密钥

这条命令会在 .env 文件下生成一个加密密钥,如:JWT_SECRET=foobar

php artisan jwt:secret

2.5 更新你的模型

如果你使用默认的 User 表来生成 token,你需要在该模型下增加一段代码

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Laravel\Lumen\Auth\Authorizable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Model implements AuthenticatableContract, AuthorizableContract, JWTSubject
{
    use Authenticatable, Authorizable;

    ...

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}

2.6 注册两个 Facade

Lumen 中没有辅助函数 auth (),这两个 Facade 就挺有用了。

bootstrap/app.php

把原先去了注释的那一行再改一下。

$app->withFacades(true, [
    'Tymon\JWTAuth\Facades\JWTAuth' => 'JWTAuth',
    'Tymon\JWTAuth\Facades\JWTFactory' => 'JWTFactory',
]);

2.7 设置 auth.php

把 \vendor\laravel\lumen-framework\config\auth.php 也复制到 项目根目录 config 文件夹(没有就新建)。

文档中有提到 Lumen 风格的配置文件这个概念

指的就是都在 .env 文件中设置各种设置项,在 \vendor\laravel\lumen-framework\config 文件夹下面的其他配置文件中也可以看到,很多配置项都有 env(设置项key, 默认值) 这个方法,有这个配置项的就可以在 .env 文件中设置 设置项=你的设置值 这样设置。

而复制到根目录 config 文件夹是 Laravel 风格的配置文件实现方式

这里我本来想尽量按 Lumen 风格实现的,但是下面这些属性默认并没有 env() 方式实现,所以我还是复制到根目录下改算了。

auth.php

按下面进行添加或修改。

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

...

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => \App\User::class
]

2.8 注册一些路由

Route::group([

    'prefix' => 'auth'

], function ($router) {

    Route::post('login', 'AuthController@login');
    Route::post('logout', 'AuthController@logout');
    Route::post('refresh', 'AuthController@refresh');
    Route::post('me', 'AuthController@me');

});

2.9 创建 token 控制器

Lumen 还精简了很多辅助函数,比如 auth 和 bcrypt 等。

可以安装 cosmicvelocity/lumen-helpers 或 albertcht/lumen-helpers 补全(建议用后者,更好安装)

或者使用上面说的两个 Facade。

AuthController.php

如果你没使用扩展补充的辅助函数,你需要这么写,不然直接用上面的 Laravel 那个

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;

class AuthController extends Controller
{
    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
        // 这里额外注意了:官方文档样例中只除外了『login』
        // 这样的结果是,token 只能在有效期以内进行刷新,过期无法刷新
        // 如果把 refresh 也放进去,token 即使过期但仍在刷新期以内也可刷新
        // 不过刷新一次作废
        $this->middleware('auth:api', ['except' => ['login']]);
        // 另外关于上面的中间件,官方文档写的是『auth:api』
        // 但是我推荐用 『jwt.auth』,效果是一样的,但是有更加丰富的报错信息返回
    }

    /**
     * Get a JWT via given credentials.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login(Request $request)
    {
        $credentials = $request->only('email', 'password');

        if (! $token = JWTAuth::attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        return $this->respondWithToken($token);
    }

    /**
     * Get the authenticated User.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function me()
    {
        return response()->json(JWTAuth::parseToken()->touser());
    }

    /**
     * Log the user out (Invalidate the token).
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        JWTAuth::parseToken()->invalidate();

        return response()->json(['message' => 'Successfully logged out']);
    }

    /**
     * Refresh a token.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        return $this->respondWithToken(JWTAuth::parseToken()->refresh());
    }

    /**
     * Get the token array structure.
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => JWTAuth::factory()->getTTL() * 60
        ]);
    }
}

关于中间件

1.0 版本以上的 jwt-auth,中间件在服务提供者中已经定义了,所以不需要额外写,按上面来即可。

下面是可用的中间件,第一二个功能一样,但是第二个不会抛出错误,第三四个功能一样,没什么区别。

tymon\jwt-auth\src\Providers\AbstractServiceProvider.php

protected $middlewareAliases = [
    'jwt.auth' => Authenticate::class,
    'jwt.check' => Check::class,
    'jwt.refresh' => RefreshToken::class,
    'jwt.renew' => AuthenticateAndRenew::class,
];

三、JWT Token 详解

1. token 的获取、使用、删除和刷新

  • 以下用 postman 演示,{{TEST}} 为 postman 全局变量:test.yfree.ccc

  • Laravel 环境下写在 api.php 中的路由默认有前缀 api

  • 下面的图是 Lumen 环境的,没有默认区前缀 api

1.1 获取 token

获取 token

JWT 完整使用详解


1.2 使用 token

有两种使用方法:

  • 加到 url 中:?token=你的token

  • 加到 header 中,建议用这种,因为在 https 情况下更安全:Authorization:Bearer 你的token

使用 token

JWT 完整使用详解


添加中间件保护的就需要使用 token 进行访问

可以使用的中间件有 auth、auth:api、jwt.auth、jwt.refresh、jwt.check、jwt.renew

关于这些中间件之间有什么差别,可以看我的另一篇文章:Laravel 辅助函数 auth 与 JWT 扩展详解

1.3 删除 token

删除 token

JWT 完整使用详解


删除 token 后,token 就会失效,无法再利用其获取数据。

1.4 刷新 token

刷新 token

JWT 完整使用详解


刷新后,旧 token 将会失效,但是你可以设置一个宽限时间,这个在后面具体说。

2. token 的组成、创建以及解析

2.1 组成

一个 JWT token 是一个字符串,它由三部分组成,头部、载荷与签名,中间用 . 分隔,例如:xxxxx.yyyyy.zzzzz

头部(header)

头部通常由两部分组成:令牌的类型(即 JWT)和正在使用的签名算法(如 HMAC SHA256 或 RSA.)。
例如:

{
  "alg": "HS256",
  "typ": "JWT"
}

然后用 Base64Url 编码得到头部,即 xxxxx

载荷(Payload)

载荷中放置了 token 的一些基本信息,以帮助接受它的服务器来理解这个 token。同时还可以包含一些自定义的信息,用户信息交换。

载荷的属性也分三类:

  • 预定义(Registered)

  • 公有(public)

  • 私有(private)

预定义的载荷

{
  "sub": "1",
  "iss": "http://localhost:8000/auth/login",
  "iat": 1451888119,
  "exp": 1454516119,
  "nbf": 1451888119,
  "jti": "37c107e4609ddbcc9c096ea5ee76c667",
  "aud": "dev"
}

这里面的前 7 个字段都是由官方所定义的,也就是预定义(Registered claims)的,并不都是必需的。

  • iss (issuer):签发人

  • sub (subject):主题

  • aud (audience):受众

  • exp (expiration time):过期时间

  • nbf (Not Before):生效时间,在此之前是无效的

  • iat (Issued At):签发时间

  • jti (JWT ID):编号

公有的载荷

在使用 JWT 时可以额外定义的载荷。为了避免冲突,应该使用 IANA JSON Web Token Registry 中定义好的,或者给额外载荷加上类似命名空间的唯一标识。

私有载荷

在信息交互的双方之间约定好的,既不是预定义载荷也不是公有载荷的一类载荷。这一类载荷可能会发生冲突,所以应该谨慎使用。

将上面的 json 进行 Base64Url 编码得到载荷,,即 yyyyy

关于载荷的理解:

这里三种载荷的定义应该明确的一点是 —— 对于后两种载荷,它并非定义了载荷的种类,然后让你去选用哪种载荷,而是对你可能会定义出来的载荷做一个分类。

比如你定义了一个 admin 载荷,这个载荷按其分类应该是私有载荷,可能会和其他人定义的发生冲突。但如果你加了一个前缀(命名空间),如 namespace-admin,那么这应该就算一个公有载荷了。(但其实标准并没有定义怎么去声明命名空间,所以严格来说,还是可能会冲突)

但是在现实中,团队都是约定好的了要使用的载荷,这样的话,好像根本不存在冲突的可能。那为什么文档要这么定义呢?我的理解是,RFC 是提出一种技术规范,出发点是一套通用的规范,考虑的范围是所有开发者,而不仅仅局限于一个开发者团队。就像用 token 做认证已经是很常见的技术了,但是 JWT 的提出就相当于提出了一套较为通用的技术规范。既然是为了通用,那么考虑在大环境下的冲突可能性也是必须的。

签名(Signature)

签名时需要用到前面编码过的两个字符串,如果以 HMACSHA256 加密,就如下:

HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
)

加密后再进行 base64url 编码最后得到的字符串就是 token 的第三部分 zzzzz

组合便可以得到 token:xxxxx.yyyyy.zzzzz

签名的作用:保证 JWT 没有被篡改过,原理如下:

HMAC 算法是不可逆算法,类似 MD5 和 hash ,但多一个密钥,密钥(即上面的 secret)由服务端持有,客户端把 token 发给服务端后,服务端可以把其中的头部和载荷再加上事先共享的 secret 再进行一次 HMAC 加密,得到的结果和 token 的第三段进行对比,如果一样则表明数据没有被篡改。

Hash-based Message Authentication Code

PHP 代码示例

// 这里要开启true
$zzzzz = $this->base64url_encode(hash_hmac('sha256', 'xxxxx.yyyyy', getenv('JWT_SECRET'), true));

protected function base64url_encode($data) {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

2.2 token 的创建

前面的 AuthController.php 中有两行展现了这一种 token 的创建方法,即用用户所给的账号和密码进行尝试,密码正确则用对应的 User 信息返回一个 token 。

但 token 的创建方法不止这一种,接下来介绍 token 的三种创建方法:

  • 基于账密参数

  • 基于 users 模型返回的实例

  • 基于 users 模型中的用户主键 id

a) 基于账密参数

这就是刚刚说的哪一种,贴出具体代码。

// 使用辅助函数
$credentials = request(['email', 'password']); 
$token = auth()->attempt($credentials)

// 使用 Facade
$credentials = $request->only('email', 'password');
$token = JWTAuth::attempt($credentials);

b) 基于 users 模型返回的实例

// 使用辅助函数
$user = User::first();
$token = auth()->login($user);

// 使用 Facade
$user = User::first();
$token = JWTAuth::fromUser($credentials);

c) 基于 users 模型中的主键 id

// 使用辅助函数
$token = auth()->tokenById(1);

// 使用 Facade
源码中没找到

2.3 token 的解析

a) 解析 token 到对象

只有 Facade 需要这样。

// 把请求发送过来的直接解析到对象
JWTAuth::parseToken();

b) 获取 token 中的 user 信息

// 辅助函数
$user = auth()->user();

// Facade
$user = JWTAuth::parseToken()->authenticate();

c) 获取 token

如果 token 被设置则会返回,否则会尝试使用方法从请求中解析 token ,如果 token 未被设置或不能解析最终返回 false。

// 辅助函数
$token = auth()->getToken();

// Facade
$token = JWTAuth::parseToken()->getToken();

更多方法可以看文章后面的附录。

d) 如果是前端

直接 base64 解码 token 的前两段即可以知道所需的信息。

3. 载荷的设置和获取

a) 载荷设置

载荷信息会在 token 解码时得到,同时越大的数组会生成越长的 token ,所以不建议放太多的数据。同时因为载荷是用 Base64Url 编码,所以相当于明文,因此绝对不能放密码等敏感信息。

$customClaims = ['foo' => 'bar', 'baz' => 'bob'];

// 辅助函数
$token = auth()->claims($customClaims)->attempt($credentials);

// Facade - 1
$token = JWTAuth::claims($customClaims)->attempt($credentials);

--- 下面两种试了好像不行,不过前面的够用了

// Facade - 2
$payload = JWTFactory::make($customClaims);
$token = JWTAuth::encode($payload);

// Facade - 3
$payload = JWTFactory::sub(123)->aud('foo')->foo(['bar' => 'baz'])->make();
$token = JWTAuth::encode($payload);

b) 载荷解析

从请求中把载荷解析出来。可以去看扩展源代码,里面还有很多的方法。

// 辅助函数
$exp = auth()->payload()->get('exp');
$json = auth()->payload()->toJson();
$array = auth()->payload()->jsonSerialize();
$sub = $array['sub'];

// Facade - 1
$payload = JWTAuth::parseToken()->getPayload();
$payload->get('sub'); // = 123
$payload['jti']; // = 'asfe4fq434asdf'
$payload('exp') // = 123456
$payload->toArray(); // = ['sub' => 123, 'exp' => 123456, 'jti' => 'asfe4fq434asdf'] etc

// Facade - 2
$exp = JWTAuth::parseToken()->getClaim('exp');

4. token 的三个时间

一个 token 一般来说有三个时间属性,其配置都在 config/jwt.php 内。

有效时间

有效时间指的的是你获得 token 后,在多少时间内可以凭这个 token 去获取内容,逾时无效。

// 单位:分钟
'ttl' => env('JWT_TTL', 60)

刷新时间

刷新时间指的是在这个时间内可以凭旧 token 换取一个新 token。例如 token 有效时间为 60 分钟,刷新时间为 20160 分钟,在 60 分钟内可以通过这个 token 获取新 token,但是超过 60 分钟是不可以的,然后你可以一直循环获取,直到总时间超过 20160 分钟,不能再获取。

这里过期后能否刷新,经 [[@Rootrl](https://learnku.com/users/433)](https://learnku.com/users/433) 指出,其实并不是这么绝对,具体细节,看我们上面 AuthController 处的代码。有详细补充
这也是一个 token 被加入黑名单之后,会存在的时间

// 单位:分钟
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160)

宽限时间

宽限时间是为了解决并发请求的问题,假如宽限时间为 0s ,那么在新旧 token 交接的时候,并发请求就会出错,所以需要设定一个宽限时间,在宽限时间内,旧 token 仍然能够正常使用。

// 宽限时间需要开启黑名单(默认是开启的),黑名单保证过期token不可再用,最好打开
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true)

// 设定宽限时间,单位:秒
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 60)

5. 关于 JWT 的讨论

5.1 为什么用 JWT?

看我的新文章 JWT 超详细分析 。

5.2 token 的刷新问题?

a) token 为什么要刷新吗?

首先 Basic Auth 是一种最简单的认证方法,但是由于每次请求都带用户名和密码,频繁的传输肯定不安全,所以才有 cookies 和 session 的运用。如果 token 不刷新,那么 token 就相当于上面的用户名 + 密码,只要获取到了,就可以一直盗用,因此 token 设置有效期并能够进行刷新是必要的。

b) token 有效期多久合适,刷新频率多久合适?

有效期越长,风险性越高,有效性越短,刷新频率越高,刷新就会存在刷新开销,所以这需要综合考虑。而且 web 端应该设置为分钟级和小时级,而移动端应该设置为天级和周级。

c) 有没有必要每次都刷新 token ?

看我的新文章 JWT 超详细分析 。

四、附录

1. JWT 的 两个 Facade

1.1 JWTAuth

JWTAuth::parseToken()->方法() 一般都可以换成 auth()->方法()

token 生成

attempt

根据 user 账密新建一个 token。

$credentials = $request->only('email', 'password');
$token = JWTAuth::attempt($credentials)

fromUser or fromSubject

根据 user 对象生成一个 token。后者是前者别名。

$user = User::find(1);
$token = JWTAuth::fromUser($user);

token 控制

refresh

更新 token。

$newToken = JWTAuth::parseToken()->refresh();

invalidate

让一个 token 无效。

JWTAuth::parseToken()->invalidate();

check

检验 token 的有效性。

if(JWTAuth::parseToken()->check()) {
    dd("token是有效的");
}

token 解析

authenticate or toUser or user

这三个效果是一样的,toUser 是 authenticate 的别名,而 user 比前两者少一个 user id 的校验,但并没有什么影响。

$user = JWTAuth::parseToken()->toUser();

parseToken

从 request 中解析 token 到对象中,以便进行下一步操作。

JWTAuth::parseToken();

getToken

从 request 中获取 token。

$token = JWTAuth::getToken();  // 这个不用 parseToken ,因为方法内部会自动执行一次

载荷控制

customClaims or claims

设置载荷的 customClaims 部分。后者是前者的别名。

$customClaims = ['sid' => $sid, 'code' => $code];
$credentials = $request->only('email', 'password');
$token = JWTAuth::customClaims($customClaims)->attempt($credentials);

getCustomClaims

获取载荷的 customClaims 部分,返回一个数组。

$customClaims = JWTAuth::parseToken()->getCustomClaims()

getPayload or payload

获取所有载荷,三个都是一样的,最后一个一般用来检验 token 的有效性

$payload = JWTAuth::parseToken()->payload();

// then you can access the claims directly e.g.
$payload->get('sub'); // = 123
$payload['jti']; // = 'asfe4fq434asdf'
$payload('exp') // = 123456
$payload->toArray(); // = ['sub' => 123, 'exp' => 123456, 'jti' => 'asfe4fq434asdf'] etc

getClaim

获取载荷中指定的一个元素。

$sub = JWTAuth::parseToken()->getClaim('sub');

1.2 JWTGuard

这个 Facade 主要进行载荷的管理,返回一个载荷对象,然后可以通过 JWTAuth 来对其生成一个 token。

// 载荷的高度自定义
$payload = JWTFactory::sub(123)->aud('foo')->foo(['bar' => 'baz'])->make();
$token = JWTAuth::encode($payload);
$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
$payload = JWTFactory::make($customClaims);
$token = JWTAuth::encode($payload);

1.3 其他一些用法

这里用 auth 的写法,因为 Laravel 有多个 guard,默认 guard 也不是 api ,所以需要写成 auth('api') 否则,auth() 即可。

设置载荷

$token = auth('api')->claims(['foo' => 'bar'])->attempt($credentials);

显示设置 token

$user = auth('api')->setToken('eyJhb...')->user();

显示设置请求

$user = auth('api')->setRequest($request)->user();

重写有效时间

$token = auth('api')->setTTL(7200)->attempt($credentials);

验证账密是否正确

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

每一步都是实验过得出的,精简到了最少步骤,一步一步照着来应该是没有任何问题的。

如果有什么不对的地方可以在评论下回复。

 1年前 评论

@skyArony 图爆了,减分不少 :joy:

 1年前 评论

@Hatcher 有空我修一下,其实也不是什么特别重要的图 :joy:

 1年前 评论

确实,之前也是踩了不少坑,但是一直没时间分享,给你打 Call

 1年前 评论

我就是被官方文档坑过,死活按照文档没走通的那个人。。感谢 LZ,我会试试你这个,到时候如果有问题再来麻烦你

 1年前 评论

有空讲一下客户端如果保护 secret 呢~

 1年前 评论

/api/auth/login

 1年前 评论
Insua

多用户表认证,有没有什么好办法呢?

 1年前 评论

@gaodevops 的确,Laravel 环境下写在 api.php 中的路由默认有前缀 api,虽然官方文档有讲,但我也加一句。

 1年前 评论

@Insua 配置文件 auth.php 里面可以为每个看守器配置 user 来源表,我猜应该可以从那下手,具体没试过。

 1年前 评论

也想知道多表用户如何使用,3 个月之前用了但是最新的版本的配置了每个不同 model 的看守器。虽然可以验证通过,但是实际测试,A 表拿到的 token,可以过 B 表的校验。然后继而放弃 tymon/jwt-auth 了 转向了 passport

 1年前 评论

有一处在我这里有问题:
JWTAuth::parseToken->invalidate();

我这的 laravel 5.5 要写成:JWTAuth::parseToken ()->invalidate (); 才正确,

另外新人请教两个问题:

1)中间件 auth:api,如果没有通过是跳转到登录页面,我要做成返回 json 代码应该怎么做?
2)调用刷新接口的时候让之前的 token 失效应该怎么做?

 1年前 评论

的确,是我写漏了,已改正。

问题 1:两种办法

  1. X-Requested-With:XMLHttpRequest 添加这个 header ,框架就会当初 ajax 请求,就会返回 json

  2. 安装 dingo ,统一设置返回格式,只要 throw 错误的地方都会以你设置的 json 格式返回

问题 2:
刷新后,显式指定旧 token,然后 logout

 1年前 评论
oyghan

写的很详细,收益。给楼主点赞 :thumbsup:

 1年前 评论

请问如果不用默认的 user 表该怎么样修改,一直没跑通

 1年前 评论

@skyArony

发现有个问题,无论是 logout () 方法,还是手动退出 auth ('api')->logout () 这样,都无法退出

 1年前 评论

@大师兄 可以用 invalidate ,你那不生效可以看看具体代码吗?

 1年前 评论

@skyArony 用 invalidate 也没有用, 源码我传到 github 上了:http://t.cn/R3T3MuU

 1年前 评论

不错哦

 1年前 评论

深度好文。

 1年前 评论

前后端分离的话,调用不同的接口,比如在登录页登录验证了,在另外一个页面进行购物车操作,购物车结算时调用另外一个借口,后台怎么进行身份证验证,证明是谁购买的?

 1年前 评论

@sunlinesun 使用 token 做身份识别

 1年前 评论

写的非常友好,很详细

 1年前 评论

一股清流,十分赞

 1年前 评论

@skyArony 我这边调用退出有些问题,只显示了退出页面,没显示 json

 1年前 评论

@skyArony
如何兼容 url 带 token?

 1年前 评论

可以稍微讲下前端登录和刷新的流程吗?

我现在用的是社区另一个人的方案,就是中间件里,如果 token 过期,自动刷新。。。

 1年前 评论

@158abcd1510 专栏里还有另一篇文章,可以看看

 1年前 评论
ChengFu

那怎么在路由 做拦截呢 就是 没有带 token 的话 就报错

 1年前 评论
Kachung-Lau

写的非常详细,感谢分享哈。

 1年前 评论

这是看过的最清晰的一篇教程了,点赞 :thumbsup::thumbsup::thumbsup::thumbsup::thumbsup::thumbsup::thumbsup:

 1年前 评论

感谢引入我的公众号 (coding01) 的文章
学习 Lumen 用户认证 (一)
https://mp.weixin.qq.com/s/KVUQE2DUetNB2kq...

 1年前 评论
dividez

file


token 的有效期是 60 分钟,60 分钟过期后, 通过下面的代码进行刷新

$newToken = JWTAuth::parseToken()->refresh();

提示 token 已经失效,

file


楼主这里说的一直循环是什么意思?token 已经过期了,循环是做什么操作呢?

 1年前 评论

@dividez 这里可能没讲清楚,我解释一下:

『循环获取』的意思是,用 A 换到 新 token B,再用 B 可以换到 新 token C,然后 C 又可以换到 新 token D。每次更新,旧 token 就会失效,当然这里可以设置一个宽限时间让 A 换到 B 后不至于马上失效。

 1年前 评论
ThinkCsly

谢谢,很好。

 1年前 评论
刷新时间
刷新时间指的是在这个时间内可以凭旧 token 换取一个新 token。例如 token 有效时间为 60 分钟,刷新时间为 20160 分钟,在 60 分钟内可以通过这个 token 获取新 token,但是超过 60 分钟是不可以的,然后你可以一直循环获取,直到总时间超过 20160 分钟,不能再获取。

楼主这里好像讲错了? “在 60 分钟内可以通过这个 token 获取新 token,但是超过 60 分钟是不可以的” 过了 60 分钟 但是在 20160 内是可以再次刷新的吧

 1年前 评论

设置 token 不过期需要怎么设置

 1年前 评论

@rootrl 并非如此,JWT token(在这个组件中的实现)是超过了 60 分钟是无法用来刷新以获取新 token 的,也就是说 token 之间的生命必须是相连的。你理解的这个,在 Oauth2.0 中的 refresh_token 上到的确是这么回事。
后续我会把 token 刷新那一块详细补充一下。

 1年前 评论

我的版本是:"tymon/jwt-auth": "^1.0.0-rc.1",我测试结果是我说的这样的。而且如果过了 60min 不能刷新,那么就不存在过期刷新这个概念了呀,就只是个纯粹的时间限制了。 比如中间件一般是捕获过期异常,然后尝试刷新。如果过期即刷新那就没这个逻辑了。总之,我现在测得是可以刷新的。不知道规范是怎么设计的?

 1年前 评论

@rootrl 我刚刚也在测,打算统一整理一下。你有设置宽限时间吗?我这如果宽限时间设置为 0,就是我说的那样的效果,但是宽限时间如果设置为 0 以上就可以刷新,不过这时还要考虑是自然过期还是手动刷新导致的过期。现在还没有测试的很清楚,待会我整理好了会添加到文章上。

 1年前 评论

@skyArony 设置了 1min 宽限时间的。好的,你可以把详细结果整理下。其实你这篇文章写的很详细的。感谢!

 1年前 评论

@rootrl 刚好公司分享就在整理这个,又给我发现了很多 jwt 的小问题。关于这个刷新,主要整理了以下几点:

  1. 官方文档的 AuthControlller 中只把 login 排除在了检测中间件之外,但这样有问题:

  • 宽限时间设置为 0 的话,token 一旦过有效期,就永远无效了,不能进行任何操作,包括刷新。

  • 宽限时间设置为 0 以上的话,token 一旦过有效期,就永远无效了,不能进行任何操作,包括刷新,可是如果有效期以内进行刷新或注销,都会把 token 的有效期设置为宽限时间,但是不会超过其有效期。

  • 应该把 refresh 操作也排除在检测中间件之外,这样更复合业务逻辑,并且这样可以在 token 过期后但是刷新期以内刷新 token

    public function __construct() {
    $this->middleware('jwt.auth', ['except' => ['login', 'refresh']]);
    }

    还有蛮多细节不好在这打出来,过几天统一添加到文章上。

  •  1年前 评论

    @skyArony 总之,这个扩展还是挺好用的。我们现在有个接口分商家和用户两套体系,然后 token 机制都是基于这个实现。目前运行没什么问题,能达到我想要的结果,包括在 header 中自动刷新 Token。期待你的更新。^_^

     1年前 评论

    @Rootrl 已更新哈,写了一篇新文章,可以看看,如果有错误,欢迎指出。

     1年前 评论

    @skyArony @Rootrl 请教一下,这个 token 过期的异常该怎么捕捉到?个人尝试了是失败。

    file


    这里不能生效,结果都是 (如图)

    file


     1年前 评论

    @god-lin 要捕获第一个图的异常得用 jwt.auth 这个中间件,至于你这个报错,没具体代码,不太清楚啥原因,检查检查路由试试。

     1年前 评论

    @erigo 你这个可能原因是你访问的 api/auth/me 路由需要鉴权,但是你现在是没带授权信息就直接访问此路由 而 auth ('api') 这套权限机制走的是以前 http 页面那套,准备把你跳到 login 页面,先登录授权。但是呢,你又没有这个 login 路由页面,所以抛出此异常。

    解决也很简单,首先你要理清你的业务逻辑。你可以在 app/Exceptions/Handler.php 的 render 方法中捕获 UnauthorizedHttpException 这个异常,在此处直接返回鉴权失败的 json 格式信息给客户端。

    另外就是 token 过期异常其实是自己定义个中间件捕获,然后此中间件还可以兼带刷新 token 功能,我这里直接命名 RefreshToken。

     1年前 评论

    @skyArony 好的 才看到 向你学习 ^_^ 我国庆也写了篇思路文章:https://rootrl.github.io/2018/10/03/%E5%85... 欢迎指正

     1年前 评论

    @skyArony @Rootrl
    现在是使用了 jwt.auth,得到了具体的 报错,也就是令牌失效,结果如图

    file

    但是并没有达到我要的目的,我这里是一个接口,不是页面跳转。(因为要实现前后端分离)现在已经可以生成令牌,进行令牌刷新,和通过令牌去进行 用户信息的返回,只是当令牌失效时,会出现异常。现在是想通过捕获或者获取到这个异常,然后接口返回 需要重新登录 的消息返回。


    1. 控制器更改 auth:api,使用 jwt.auth (可以获取到更详细的 原因:令牌失效)

      file

    2. 通过 token 获取用户相关的信息,(token 不过期是可以 正常返回 用户信息 )路由设置和对应的方法代码

    file


    file


    • 应该是路由检查方法时,在这里 throw 了异常,然后就是这里的异常怎么自己获取到,然后在自己的接口返回具体的消息

    file

    就是这样了,这里的异常可以通过什么方法自己获取到,不要直接 报 这样的错误 吗


    file


     1年前 评论

    @god-lin 不好意思 我回答你了 只是 at 错了人。。你仔细看我给你的回答 上面有捕获异常方法

    你就你上面第二条

     1年前 评论
    snail404

    我测试的结果也是,token 过期,在刷新时间为 20160 分钟内,也是可以刷新的,与设置宽限时间无关,我的理解是,中间件,检测 token 失效,失效后刷新 token 捕捉是否抛出异常,没有就在刷新期内,就正常使用,我现在测试有个问题就是可以刷新,旧的 token 在刷新后也正常加入,但是这个被加入黑名单后的 token,无法进行后面的验证操作,我设置了宽限时间,应该是在宽限时间内是可以正常使用的吧?@skyArony @Rootrl

     1年前 评论

    @snail404 中间件检测失效,失效就刷新重新返回 token,此时客户端应该有个检测服务端是否返回 token 的逻辑,有就刷新本地 token。走这套模式,不存在旧的 token 问题。

     1年前 评论
    snail404

    @Rootrl 在调用接口操作是,我这个 token 失效但还在刷新时间内,应该可用,然后会继续下面得操作,响应同时会附带新得 token

     1年前 评论
    snail404

    @Rootrl 这样减少一次得请求操作,我理解得逻辑应该是这样,然后那个黑名单得宽限时间,就允许接下来得正常操作(因为在刷新得同时将旧得加入黑名单),就不确定是不是我理解问题,还是代码使用问题

     1年前 评论

    @snail404 是这样呀,本次的请求能顺利完成,比如黑名单一般有一分钟左右期限,这期中都有效的。

     1年前 评论
    snail404

    @Rootrl 我现在每次刷新的时候,下面的操作 token 就不可用,提示 The token has been blacklisted,黑名单设置跟时间是有开启的,下一次的请求也是校验不通过的,你有没有遇到过这个问题?

     1年前 评论
     gengwenlong 2个月前 

    @snail404 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
    'blacklist_grace_period' => env ('JWT_BLACKLIST_GRACE_PERIOD', 60), 这两项是这样么?

     1年前 评论
    snail404

    @Rootrl 对,是这样的

     1年前 评论

    @snail404 那就不清楚了,我这边能达到我要的效果。你调试下,在程序中打印下这配置。

     1年前 评论
    snail404

    好的,多谢

     1年前 评论

    请问如果 auth 失效了
    php 'middleware' => ['auth:api'] 
    加上这个中间件后前端就没办法取到这个 http 错误码 401 了吗

     1年前 评论

    @monch 这个会返回 500

     1年前 评论

    完全按照教程上操作,laravel5.6 和 5.7 都提示没有 respondWithToken 这个方法。请问原因.

     1年前 评论

    如何修改验证字段不为 email

    use AuthenticatesUsers;
    $credentials = request(['uname', 'password']);
        public function username()
    {
        return 'uname';
    }
     1年前 评论

    auth ('api')->user () 会返回模型的所有属性,有没有办法可以控制让它只返回自己需要的属性,比如我只需要 id,username,其他的属性不需要返回,要怎么处理

     1年前 评论

    Laravel 辅助函数 auth 与 JWT 扩展详解 这个链接失效了!

     1年前 评论

    Laravel 辅助函数 auth 与 JWT 扩展详解 楼主能补一下链接吗,谢谢了。很想看!

     1年前 评论

    @muqi001 补上了,其实在我专栏里就可以找到

     1年前 评论
    seeker-x2y

    @skyArony 您好!请教个问题,如果我整站用户角色比较多,采用前后端分离的方式来开发,是否可以将前后台所有用户的基础信息(用户名、密码)放置在一个表(users)中做 jwt 的认证呢?而非采用多个 guard 的方式?

     1年前 评论

    @god-lin 不知是否已经解决,但是可以提供一个其他的思路。那个报错其实是当 token 失效或无效时帮你自动跳转到了被命名为 login 的路由当中。但是如果写接口或新创建的项目默认是没有这个路由的,所以就报错了。
    你完全可以直接创建一个针对此异常返回的一个路由然后命名为 login 。这样每次遇到这种情况就会帮你跳转到你的 login 路由了。

        Route::get('/unauthorized', function () {
            return response()->json(['error' => 'Unauthorized'], 401);
        })->name('login');
     1年前 评论

    @Echoiii dd(auth('api')->user()) 可以看出这个返回的是 User 模型。所以对应 User 模型和集合的方法是都可以用的。比如只想要 name 就可以 auth('api')->user()->only(['name']) 还有一些 User 模型的资源修改的方法都可以尝试下。

     1年前 评论

    @大师兄 JWTAuth::invalidate(JWTAuth::getToken());

     11个月前 评论

    赞,学到了,不过 laravel5.5 里的 kernel 的 $routeMiddleware 是这样注册的,但是编辑器提示不对。。。
    'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
    'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class,

     10个月前 评论

    @键盘侠 看清楚、是博主自定义的方法

     10个月前 评论

    密码的加密方式能修改吗?如果能修改应该如何操作?

     10个月前 评论

    请问一下 public function login ()
    {
    $credentials = request(['email', 'password']);
    if (! $token = auth('api')->attempt($credentials)) {
    return response()->json(['error' => 'Unauthorized'], 401);
    }

            return $this->respondWithToken($token);
    }

    这一步的 的输出结果总是 Unauthorized 这个验证用的什么来验证 email 和 password

     10个月前 评论
     isrocky 5个月前 

    楼主你好~我在 RefreshToken 中间件中,仿照官网使用 try:$result = auth ('api')->authenticate (),然后 catch 过期 token 的 TokenExpiredException,但是 token 虽然过期了但貌似并没有抛出这个异常,我现在无法捕获到 token 过期的异常,请问应该怎样做?谢谢了

     9个月前 评论
     13697332484 4周前 

    如何更换验证?数据库中没有 email 和 password,想要换成 user_phone 和 user_password

     8个月前 评论

    能不能有个详细的,token 过期刷新的中间件详解?
    如何返回新的 token?修改 header
    现在不知道怎么弄这个中间件,jwt.refresh','jwt.renew' 这两个中间件一般在说明时候用?

     8个月前 评论

    微信 浏览器 auth ('api')->user () 返回 null 什么原因

     8个月前 评论

    文章中好像没有提到,做个记号,想要根据一个 token 串获取用户是否已认证,可以使用:

    $ok = JWTAuth::setToken('your_token_string')->check(); // true or false
     7个月前 评论
    NaturalGao

    我想问下用 Facade 怎么指定 guards?

    解决办法: 在构造方法中 动态修改默认的 guards

        public function __construct()
        {
            config(['auth.defaults.guard' => 'admin','auth.defaults.passwords' => 'admin']);
        }
     7个月前 评论
    duke

    不用默认的 user 模型,用 laravel-admin 的 admin_users 报错,Argument 1 passed to Tymon\JWTAuth\JWT::fromUser () must be an instance of Tymon\JWTAuth\Contracts\JWTSubject, instance of Encore\Admin\Auth\Database\Administrator given, called in /Applications/MAMP/htdocs/baccarat-manage/vendor/tymon/jwt-auth/src/JWTAuth.php on line 54

     7个月前 评论
     13697332484 4周前 

    中间件不管使用 auth:api 还是 jwt.auth,认证的时候都是报 "Method Illuminate\Auth\RequestGuard::onceUsingId does not exist.",lumen 5.8 + jwt 1.4 有遇到过的吗?看了源码,不太理解,才发问求解决。

     7个月前 评论
    NaturalGao

    lumen 5.8 之前验证还可以得 现在不可以了

     7个月前 评论

    因为数据库加密的方式是 md5 的,不知道在登录验证 api/auth/login 这个地方去验证的时候密码是什么加密方式;这个地方的加密方式需要一致吗?
    $credentials = request(['email', 'password']);
    auth('api')->attempt($credentials)

    file


     6个月前 评论
     isrocky 4个月前 
     1164880236 (作者) 4个月前 
     mantou_1 2个月前 

    请问下,invalidate () 方法是否是使 token 临时失效吗?我使用一个 token ,第一次请求时,使它失效,然后第二次还是用这个 token, 这个 token 依然有效。

    file


     6个月前 评论

    file

    请问下 lumen 框架在这个步骤里 无法生成 token 一般是由哪些地方引起的呢


     5个月前 评论
     isrocky 5个月前 

    好些图片都挂了

     5个月前 评论

    用户登录后怎么把之前的 token 删除

     5个月前 评论

    看来大家都有类似的问题

     4个月前 评论

    请问下,lumen 安装 jwt 之后 php artisan list 找不到 jwt 相关的命令,这是怎么回事?lumen 版本 5.8.12,jwt 版本 1.0.0-rc.4.1

    重新安装之后解决了。

     4个月前 评论

    这个 jwt 可以跟 java 的一起用吗?

     2个月前 评论

    请问一下如何使用自己的模型来构建?

    file


     2个月前 评论

    JWT 就是垃圾,我修改了 默认的 user 表为 admin 表,新增了一个 apijwt 的 guard,驱动是 jwt,model 是 App\Admin,结果 token 能生成,但是使用默认 JWTAuth:: 方法,结果 admin 表用户 token 还是跑到 user 表下了。 只能使用 auth ('apijwt')->user()才能拿到 admin 的用户,然后各种 checkOrFail、parseToken 没反应,设置了 TTL 为 1 分钟,根本就不过期,也走不到 expiredException 里面来。醉了

    不是 JWT 垃圾,是 tymon/jwtauth 这个包垃圾。

     4周前 评论

    刷新时间那里我觉得没说清楚

    刷新时间指的是在这个时间内可以凭旧 token 换取一个新 token。例如 token 有效时间为 60 分钟,刷新时间为 20160 分钟,在 60 分钟内可以通过这个 token 获取新 token,但是超过 60 分钟是不可以的,然后你可以一直循环获取,直到总时间超过 20160 分钟,不能再获取。

    我觉得应该这样解释:
    刷新时间指的是在这个时间内可以凭旧 token 换取一个新 token。例如 token 有效时间为 60 分钟,刷新时间为 20160 分钟。代表你的 token 在 60 分钟内可以正常使用,超过 60 分钟则过期无法使用。但在 20160 分钟的期限内你可以在任意时刻凭旧 token 换取新 token。

     2周前 评论

    请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!


    来自  https://learnku.com/articles/10885/full-use-of-jwt

    普通分类: