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
1.2 使用 token
有两种使用方法:
- 加到 url 中:
?token=你的token
- 加到 header 中,建议用这种,因为在 https 情况下更安全:
Authorization:Bearer 你的token
使用 token
添加中间件保护的就需要使用 token进行访问
可以使用的中间件有 auth、auth:api、jwt.auth、jwt.refresh、jwt.check、jwt.renew
关于这些中间件之间有什么差别,可以看我的另一篇文章:Laravel 辅助函数 auth 与 JWT 扩展详解
1.3 删除 token
删除 token
删除 token
后,token就会失效,无法再利用其获取数据。
1.4 刷新 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](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 协议》,转载必须注明作者和本文链接
高认可度评论:
每一步都是实验过得出的,精简到了最少步骤,一步一步照着来应该是没有任何问题的。
如果有什么不对的地方可以在评论下回复。
每一步都是实验过得出的,精简到了最少步骤,一步一步照着来应该是没有任何问题的。
如果有什么不对的地方可以在评论下回复。
@skyArony 图爆了,减分不少 :joy:
@Hatcher 有空我修一下,其实也不是什么特别重要的图 :joy:
确实,之前也是踩了不少坑,但是一直没时间分享,给你打Call
我就是被官方文档坑过,死活按照文档没走通的那个人。。感谢LZ,我会试试你这个,到时候如果有问题再来麻烦你
有空讲一下客户端如果保护 secret 呢~
/api/auth/login
多用户表认证,有没有什么好办法呢?
@gaodevops 的确,Laravel 环境下写在
api.php
中的路由默认有前缀api
,虽然官方文档有讲,但我也加一句。@Insua 配置文件
auth.php
里面可以为每个看守器配置 user 来源表,我猜应该可以从那下手,具体没试过。也想知道多表用户如何使用,3个月之前用了但是最新的版本的配置了每个不同model的看守器。虽然可以验证通过,但是实际测试,A表拿到的token,可以过B表的校验。然后继而放弃tymon/jwt-auth了 转向了 passport
有一处在我这里有问题:
JWTAuth::parseToken->invalidate();
我这的 laravel 5.5 要写成:JWTAuth::parseToken()->invalidate(); 才正确,
另外新人请教两个问题:
1)中间件 auth:api,如果没有通过是跳转到登录页面,我要做成返回json代码应该怎么做?
2)调用刷新接口的时候让之前的token失效应该怎么做?
的确,是我写漏了,已改正。
问题1:两种办法
X-Requested-With:XMLHttpRequest
添加这个 header ,框架就会当初 ajax 请求,就会返回 json问题2:
刷新后,显式指定旧 token,然后 logout
写的很详细,收益。给楼主点赞 :+1:
请问如果不用默认的 user 表该怎么样修改,一直没跑通
@skyArony
发现有个问题,无论是logout() 方法,还是手动退出 auth('api')->logout() 这样,都无法退出
@大师兄 可以用 invalidate ,你那不生效可以看看具体代码吗?
@skyArony 用invalidate也没有用, 源码我传到github上了:http://t.cn/R3T3MuU
不错哦
深度好文。
@sunlinesun 使用token做身份识别
写的非常友好,很详细
一股清流,十分赞
@skyArony 我这边调用退出有些问题,只显示了退出页面,没显示json
@skyArony
如何兼容url带token?
可以稍微讲下前端登录和刷新的流程吗?
我现在用的是社区另一个人的方案,就是中间件里,如果
token
过期,自动刷新。。。@158abcd1510 专栏里还有另一篇文章,可以看看
那怎么在路由 做拦截呢 就是 没有带token的话 就报错
写的非常详细,感谢分享哈。
这是看过的最清晰的一篇教程了,点赞 :+1::+1::+1::+1::+1::+1::+1:
感谢引入我的公众号 (coding01)的文章
学习 Lumen 用户认证 (一)
https://mp.weixin.qq.com/s/KVUQE2DUetNB2kq...
token的有效期是 60分钟,60分钟过期后, 通过下面的代码进行刷新
提示token 已经失效,
楼主这里说的一直循环是什么意思?token 已经过期了,循环是做什么操作呢?
@dividez 这里可能没讲清楚,我解释一下:
『循环获取』的意思是,用 A 换到 新 token B,再用 B 可以换到 新 token C,然后 C 又可以换到 新 token D。每次更新,旧 token 就会失效,当然这里可以设置一个宽限时间让 A 换到 B 后不至于马上失效。
谢谢,很好。
楼主这里好像讲错了? “在 60 分钟内可以通过这个 token 获取新 token,但是超过 60 分钟是不可以的” 过了60分钟 但是在20160内是可以再次刷新的吧
设置token不过期需要怎么设置
@rootrl 并非如此,JWT token(在这个组件中的实现)是超过了60分钟是无法用来刷新以获取新 token 的,也就是说 token 之间的生命必须是相连的。你理解的这个,在 Oauth2.0中的 refresh_token 上到的确是这么回事。
后续我会把 token 刷新那一块详细补充一下。
我的版本是:"tymon/jwt-auth": "^1.0.0-rc.1",我测试结果是我说的这样的。而且如果过了60min不能刷新,那么就不存在过期刷新这个概念了呀,就只是个纯粹的时间限制了。 比如中间件一般是捕获过期异常,然后尝试刷新。如果过期即刷新那就没这个逻辑了。总之,我现在测得是可以刷新的。不知道规范是怎么设计的?
@rootrl 我刚刚也在测,打算统一整理一下。你有设置宽限时间吗?我这如果宽限时间设置为0,就是我说的那样的效果,但是宽限时间如果设置为 0 以上就可以刷新,不过这时还要考虑是自然过期还是手动刷新导致的过期。现在还没有测试的很清楚,待会我整理好了会添加到文章上。
@skyArony 设置了1min宽限时间的。好的,你可以把详细结果整理下。其实你这篇文章写的很详细的。感谢!
@rootrl 刚好公司分享就在整理这个,又给我发现了很多 jwt 的小问题。关于这个刷新,主要整理了以下几点:
官方文档的 AuthControlller 中只把 login 排除在了检测中间件之外,但这样有问题:
还有蛮多细节不好在这打出来,过几天统一添加到文章上。
@skyArony 总之,这个扩展还是挺好用的。我们现在有个接口分商家和用户两套体系,然后token机制都是基于这个实现。目前运行没什么问题,能达到我想要的结果,包括在header中自动刷新Token。期待你的更新。^_^
@Rootrl 已更新哈,写了一篇新文章,可以看看,如果有错误,欢迎指出。
@skyArony @Rootrl 请教一下,这个token 过期的异常该怎么捕捉到?个人尝试了是失败。
这里不能生效,结果都是 (如图)
@god-lin 要捕获第一个图的异常得用
jwt.auth
这个中间件,至于你这个报错,没具体代码,不太清楚啥原因,检查检查路由试试。@erigo 你这个可能原因是你访问的api/auth/me路由需要鉴权,但是你现在是没带授权信息就直接访问此路由 而auth('api')这套权限机制走的是以前http页面那套,准备把你跳到login页面,先登录授权。但是呢,你又没有这个login路由页面,所以抛出此异常。
解决也很简单,首先你要理清你的业务逻辑。你可以在app/Exceptions/Handler.php 的render方法中捕获UnauthorizedHttpException这个异常,在此处直接返回鉴权失败的json格式信息给客户端。
另外就是token过期异常其实是自己定义个中间件捕获,然后此中间件还可以兼带刷新token功能,我这里直接命名RefreshToken。
@skyArony 好的 才看到 向你学习^_^ 我国庆也写了篇思路文章:https://rootrl.github.io/2018/10/03/%E5%85... 欢迎指正
@skyArony @Rootrl

现在是使用了 jwt.auth,得到了具体的 报错,也就是令牌失效,结果如图
但是并没有达到我要的目的,我这里是一个接口,不是页面跳转。(因为要实现前后端分离)现在已经可以生成令牌,进行令牌刷新,和通过令牌去进行 用户信息的返回,只是当令牌失效时,会出现异常。现在是想通过捕获或者获取到这个异常,然后接口返回 需要重新登录 的消息返回。
就是这样了,这里的异常可以通过什么方法自己获取到,不要直接 报 这样的错误 吗
@god-lin 不好意思 我回答你了 只是at错了人。。你仔细看我给你的回答 上面有捕获异常方法
你就你上面第二条
我测试的结果也是,token 过期,在刷新时间为 20160 分钟内,也是可以刷新的,与设置宽限时间无关,我的理解是,中间件,检测token 失效,失效后刷新token 捕捉是否抛出异常,没有就在刷新期内,就正常使用,我现在测试有个问题就是可以刷新,旧的token 在刷新后也正常加入,但是这个被加入黑名单后的token,无法进行后面的验证操作,我设置了宽限时间,应该是在宽限时间内是可以正常使用的吧?@skyArony @Rootrl
@snail404 中间件检测失效,失效就刷新重新返回token,此时客户端应该有个检测服务端是否返回token的逻辑,有就刷新本地token。走这套模式,不存在旧的token问题。
@Rootrl 在调用接口操作是,我这个token 失效但还在刷新时间内,应该可用,然后会继续下面得操作,响应同时会附带新得token
@Rootrl 这样减少一次得请求操作,我理解得逻辑应该是这样,然后那个黑名单得宽限时间,就允许接下来得正常操作(因为在刷新得同时将旧得加入黑名单),就不确定是不是我理解问题,还是代码使用问题
@snail404 是这样呀,本次的请求能顺利完成,比如黑名单一般有一分钟左右期限,这期中都有效的。
@Rootrl 我现在每次刷新的时候,下面的操作token 就不可用,提示 The token has been blacklisted,黑名单设置跟时间是有开启的,下一次的请求也是校验不通过的,你有没有遇到过这个问题?
@snail404 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 60), 这两项是这样么?
@Rootrl 对,是这样的
@snail404 那就不清楚了,我这边能达到我要的效果。你调试下,在程序中打印下这配置。
好的,多谢
请问如果auth失效了
php 'middleware' => ['auth:api']
加上这个中间件后前端就没办法取到这个http错误码401了吗
@monch 这个会返回 500
完全按照教程上操作,laravel5.6和5.7 都提示没有respondWithToken 这个方法. 请问原因.
如何修改验证字段不为email
auth('api')->user() 会返回模型的所有属性,有没有办法可以控制让它只返回自己需要的属性,比如我只需要id,username,其他的属性不需要返回,要怎么处理
Laravel 辅助函数 auth 与 JWT 扩展详解 这个链接失效了!
Laravel 辅助函数 auth 与 JWT 扩展详解 楼主能补一下链接吗,谢谢了。很想看!
@muqi001 补上了,其实在我专栏里就可以找到
@skyArony 您好!请教个问题,如果我整站用户角色比较多,采用前后端分离的方式来开发,是否可以将前后台所有用户的基础信息(用户名、密码)放置在一个表(
users
)中做jwt
的认证呢?而非采用多个guard
的方式?@god-lin 不知是否已经解决,但是可以提供一个其他的思路。那个报错其实是当token失效或无效时帮你自动跳转到了被命名为 login 的路由当中。但是如果写接口或新创建的项目默认是没有这个路由的,所以就报错了。
你完全可以直接创建一个针对此异常返回的一个路由然后命名为 login 。这样每次遇到这种情况就会帮你跳转到你的login路由了。
@Echoiii
dd(auth('api')->user())
可以看出这个返回的是User
模型。所以对应User
模型和集合的方法是都可以用的。比如只想要name
就可以auth('api')->user()->only(['name'])
还有一些User
模型的资源修改的方法都可以尝试下。@大师兄 JWTAuth::invalidate(JWTAuth::getToken());
赞,学到了,不过laravel5.5里的kernel的$routeMiddleware是这样注册的,但是编辑器提示不对。。。
'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class,
@键盘侠 看清楚、是博主自定义的方法
密码的加密方式能修改吗?如果能修改应该如何操作?
请问一下 public function login()
{
$credentials = request(['email', 'password']);
if (! $token = auth('api')->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
这一步的 的输出结果总是 Unauthorized 这个验证用的什么来验证email和password
楼主你好~我在RefreshToken中间件中,仿照官网使用try:$result = auth('api')->authenticate(),然后catch过期token的TokenExpiredException,但是token虽然过期了但貌似并没有抛出这个异常,我现在无法捕获到token过期的异常,请问应该怎样做?谢谢了
如何更换验证?数据库中没有email和password,想要换成user_phone和user_password
能不能有个详细的,token 过期刷新的中间件详解?
如何返回新的token?修改header
现在不知道怎么弄这个中间件,jwt.refresh','jwt.renew' 这两个中间件一般在说明时候用?
微信 浏览器 auth('api')->user() 返回null 什么原因
文章中好像没有提到,做个记号,想要根据一个token串获取用户是否已认证,可以使用:
我想问下用 Facade 怎么指定 guards?
解决办法: 在构造方法中 动态修改默认的guards
不用默认的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
中间件不管使用auth:api还是jwt.auth,认证的时候都是报"Method Illuminate\Auth\RequestGuard::onceUsingId does not exist.",lumen 5.8 + jwt 1.4有遇到过的吗?看了源码,不太理解,才发问求解决。
lumen 5.8 之前验证还可以得 现在不可以了
因为数据库加密的方式是md5的,不知道在登录验证api/auth/login 这个地方去验证的时候密码是什么加密方式;这个地方的加密方式需要一致吗?
$credentials = request(['email', 'password']);
auth('api')->attempt($credentials)
请问下,invalidate() 方法是否是使 token 临时失效吗?我使用一个 token ,第一次请求时,使它失效,然后第二次还是用这个 token,这个 token 依然有效。
请问下lumen框架在这个步骤里 无法生成token 一般是由哪些地方引起的呢
好些图片都挂了
用户登录后怎么把之前的token删除
看来大家都有类似的问题
请问下,lumen 安装jwt之后 php artisan list 找不到jwt相关的命令,这是怎么回事?lumen版本5.8.12,jwt 版本1.0.0-rc.4.1
重新安装之后解决了。
这个jwt可以跟java的一起用吗?
请问一下如何使用自己的模型来构建?
刷新时间那里我觉得没说清楚
我觉得应该这样解释:
刷新时间指的是在这个时间内可以凭旧 token 换取一个新 token。例如 token 有效时间为 60 分钟,刷新时间为 20160 分钟。代表你的token在60分钟内可以正常使用,超过60分钟则过期无法使用。但在20160 分钟的期限内你可以在任意时刻凭旧 token 换取新 token。
b) 基于 users 模型返回的实例#
// 使用辅助函数
$user = User::first();
$token = auth()->login($user);
// 使用 Facade
$user = User::first();
$token = JWTAuth::fromUser($credentials);
这里的
$credentials
是不是应该是$user
?我想问问大家,那没通过验证的呢?比如用户未携带token或者token已过期访问的情况下?在哪报错?框架默认是直接让跳转到login路由的,你们都怎么处理的?
这里有个坑,使用辅助函数获取用户token会报错
报错:
方法中并未使用fromUser方法,直接用fromUser(User对象)登录没有问题,我的用户模型为App\User\Models,也实现了JWTSubject接口。
然后找问题,找到在app目录下有一个User.php(使用了laravel-admin),直接删除也会报错,通过全局搜索发现在:
这些地方中有用到,解决方法就是要么就是改指向的用户模型,要么就是在App\User中也实现JWTSubject接口。我用的是方法二,最后得出的结果:
Class ‘Tymon\JWTAuth\Providers\LumenServiceProvider’ not found
ErrorException: Non-static method Tymon\JWTAuth\JWT::invalidate() should not be called statically in file 出现这个是什么原因?Google 都没有找到答案。laravel6. 'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth', 'JWTFactory' => 'Tymon\JWTAuth\Facades\JWTFactory', 这两个已经引入了。是不是providers要引入什么?
为何我不同的guard,用同一个token居然也是可以的


单点登录如何实现?比如同一个账号密码,不同设备。
laravel7,auth()->login($user);方法不存在!
我这样该auth中间件可以吗?
我使用了jwt.auth中间件,我再怎么将抛出的异常以JSON格式返回呢?
if (! $token = auth('api')->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
token = auth('api')->attempt($credentials) 返回false,结果返回
{
"error": "Unauthorized"
}
刷新时间问题 刷新时间超时的话直接抛出500 状态码. 这个状态码 在哪里修改呢..指针对 刷新时间到期的问题.
Could not create token: Implicit conversion of keys from strings is deprecated. Please use InMemory or LocalFileReference classes.
同一个项目两个电脑 clone 更新composser 后其中一个报错
您好,按照你的步骤 最后验证返回false,获取的token为false,请问下怎么解决 if (! $token = auth('api')->attempt($credentials)) { return response()->json(['error' => 'Unauthorized'], 401); }
token = auth ('api')->attempt ($credentials) 返回 false,结果返回 { "error": "Unauthorized" }
你好,我是一个初学者,使用的tp框架,在使用jwt3.3的时候遇到了问题
感谢楼主指点,自己看文档走的着实费劲。
中间件已弃用,请问怎么办?