JWT 完整使用详解
256

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-helpersalbertcht/lumen-helpers 补全(建议用后者,更好安装)

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

AuthController.php

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

<?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获取token

1.2 使用 token

有两种使用方法:

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

使用 token使用 token

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

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

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

1.3 删除 token

删除 token删除 token

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

1.4 刷新 token

刷新 token刷新 token

刷新后,旧 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 指出,其实并不是这么绝对,具体细节,看我们上面 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 是一种最简单的认证方法,但是由于每次请求都带用户名和密码,频繁的传输肯定不安全,所以才有 cookiessession 的运用。如果 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

这三个效果是一样的,toUserauthenticate 的别名,而 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);
本帖由系统于 6个月前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 64

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

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

6个月前

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

6个月前

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

6个月前

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

6个月前

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

6个月前

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

6个月前

/api/auth/login

6个月前
Insua

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

6个月前

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

6个月前

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

6个月前
Yu

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

6个月前

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

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

另外新人请教两个问题:

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

6个月前

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

问题1:两种办法

  1. X-Requested-With:XMLHttpRequest 添加这个 header ,框架就会当初 ajax 请求,就会返回 json
  2. 安装 dingo ,统一设置返回格式,只要 throw 错误的地方都会以你设置的 json 格式返回

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

6个月前
oyghan

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

6个月前

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

6个月前

@skyArony

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

6个月前

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

5个月前

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

5个月前

不错哦

5个月前

深度好文。

5个月前

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

5个月前

@sunlinesun 使用token做身份识别

5个月前

写的非常友好,很详细

4个月前

一股清流,十分赞

4个月前

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

4个月前

@skyArony
如何兼容url带token?

4个月前

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

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

4个月前

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

4个月前

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

4个月前
Liujiacong

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

4个月前

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

3个月前

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

3个月前
dividez

file

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

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

提示token 已经失效,

file

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

3个月前

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

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

3个月前

谢谢,很好。

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 的有效期设置为宽限时间,但是不会超过其有效期。
  2. 应该把 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 @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个月前

@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了吗

3周前

@monch 这个会返回 500

3周前

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

1周前

如何修改验证字段不为email

2天前

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!