JWT全称JSON Web Tokens,是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。它的两大使用场景是:认证和数据交换。
第一步、下载composer
# 建议使用1.0以上版本
composer require tymon/jwt-auth 1.*@rc
第二步、生成文件、修改配置
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider
php artisan jwt:secret
[root@localhost laravel_base]# php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
Copied File [/vendor/tymon/jwt-auth/config/config.php] To [/config/jwt.php]
Publishing complete.
[root@localhost laravel_base]# php artisan jwt:secret
jwt-auth secret [Jyxc5QkfSSEHNv7XC6wW4YDw0HvBAoApPYfGhnyv5bhylmhyGSw8U44r1FKonviY] set successfully.
2.1 发布配置文件
# 这条命令会在 config 下增加一个 jwt.php 的配置文件
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
2.2 生成加密密钥
# 这条命令会在 .env 文件下生成一个加密密钥,如:JWT_SECRET=foobar
php artisan jwt:secret[root@localhost laravel_base]# php artisan jwt:secret
This will invalidate all existing tokens. Are you sure you want to override the secret key? (yes/no) [no]:
> yes
jwt-auth secret [4Zule0z1Fy1BMPbW6MvCmFLhZV9ym9QaWlRqCft1BbMkvjLpmYWQ4akLi7hFSHE3] set successfully.
2.3 更新模型
如果你使用默认的 User 表来生成 token,你需要在该模型下增加一段代码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::class,
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
],
如果你不使用这两个 Facade,你可以使用辅助函数 auth ()
auth () 是一个辅助函数,返回一个 guard,暂时可以看成 Auth Facade。// 如果你不用 Facade,你可以这么写
auth('api')->refresh();
// 用 JWTAuth Facade
JWTAuth::parseToken()->refresh();
如果你不使用这两个 Facade,你可以使用辅助函数 auth ()
auth () 是一个辅助函数,返回一个 guard,暂时可以看成 Auth Facade。
2.5 修改 auth.php
config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt', // 原来是 token 改成jwt
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class, //如果你的providers 不是这个User class类,那么需要指定到你的class 文件Z:\laravel_base\config\auth.php
],
// 'users' => [
// 'driver' => 'database',
// 'table' => '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 新建控制器类<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Tymon\JWTAuth\JWT;
use Tymon\JWTAuth\JWTAuth;
class AuthController extends Controller {
/**
* Create a new AuthController instance.
* 要求附带email和password(数据来源users表)
*
* @return void
*/
public function __construct() {
// 这里额外注意了:官方文档样例中只除外了『login』
// 这样的结果是,token 只能在有效期以内进行刷新,过期无法刷新
// 如果把 refresh 也放进去,token 即使过期但仍在刷新期以内也可刷新
// 不过刷新一次作废
$this->middleware('jwt.auth', ['except' => ['login']]);
// 另外关于上面的中间件,官方文档写的是『auth:api』
// 但是我推荐用 『jwt.auth』,效果是一样的,但是有更加丰富的报错信息返回
}
/**
* Get a JWT via given credentials.
*
* @return \Illuminate\Http\JsonResponse
*/
public function login() {
//$data=User::active()->get();
//
//return $data;
//
//print_r(request()->input('access_token'));
//
//exit;
$credentials = request(['email', 'password']);
//$credentials = request(['name', '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
]);
}
}
这里面只有一个login是不验证的,其他方法都是验证token的,
另外注意一点,且必须包含一个password字段,这是不能丢弃的,否则jwt插件用不了password
我们明文传递上来,保存在数据库中的字段是\Hash::make('123456');
加密的一个字符串
有了以上的配置几点,看一下运行结果:
看实际调用
login登录
access_token 成功了
token的类型是 bearer
获取用户
auth/me
要访问auth/me必须传递token 串,传递方式 header 头的形式
Authorization 名字
bearer 这是类型,token字符串eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC8xOTIuMTY4LjMxLjEyODo4OTAxXC9hcGlcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNTU5MTc0NDg1LCJleHAiOjE1NTkxNzgwODUsIm5iZiI6MTU1OTE3NDQ4NSwianRpIjoidU9KT3ZMaVBCWkRhYkk2dyIsInN1YiI6MSwicHJ2IjoiODdlMGFmMWVmOWZkMTU4MTJmZGVjOTcxNTNhMTRlMGIwNDc1NDZhYSJ9.ImxOqXPB6J8L-j8x0VqIuLnOcOxpjOJ1d4Tx_i2mAOg
如果字符串传递错误,就会返回
Unauthorized401
auth("api") 会返回Tymon\JWTAuth\JWTGuard
这样的一个类,里面有具体的方式和方法
具体就到这里。
官方手册:
其他知识普及:
token的创建 不止一种,接下来介绍token的三种创建方法:
1、基于账号和密码参数
2、基于user模型返回的实例
3、基于users模型中的用户主键id
1)基于账号和密码参数// 使用辅助函数
$credentials = request(['email', 'password']);
$token = auth()->attempt($credentials)
// 使用 Facade
$credentials = $request->only('email', 'password');
$token = JWTAuth::attempt($credentials);
2)基于user模型返回的实例// 使用辅助函数
$user = User::where('username','=','admin')->where('password','=','password123')->first();
$token = auth()->login($user);
// 使用 Facade
$user = User::first();
$token = JWTAuth::fromUser($user);
3)基于users模型中的用户主键id
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 的前两段即可以知道所需的信息。
头部(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"
}
这里面的前6个字段都是由JWT的标准所定义的,也就是预定义(Registered claims)的。
sub: 该JWT所面向的用户
iss: 该JWT的签发者
iat(issued at): 在什么时候签发的token
exp(expires): token什么时候过期
nbf(not before):token在此时间之前不能被接收处理
jti:JWT ID为web token提供唯一标识
将上面的 json 进行 Base64Url 编码得到载荷,,即 yyyyy。
签名(Signature)
签名时需要用到前面编码过的两个字符串,如果以 HMACSHA256 加密,就如下:HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
加密后再进行 base64url 编码最后得到的字符串就是 token 的第三部分 zzzzz。
组合便可以得到 token:xxxxx.yyyyy.zzzzz。
a) 载荷设置
载荷信息会在 token 解码时得到,同时越大的数组会生成越长的 token ,所以不建议放太多的数据。同时因为载荷是用 Base64Url 编码,所以相当于明文,因此绝对不能放密码等敏感信息。$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
// 辅助函数
$token = auth()->claims($customClaims)->attempt($credentials);
// Facade - 1
$token = JWTAuth::claims($customClaims)->attempt($credentials);
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 分钟,不能再获取。
// 单位:分钟
'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)
JWT 完整使用详解
这是一个完整版的jwt非常详细 ,推荐查看:
其他逻辑,https://learnku.com/articles/6216/laravel-uses-jwt-to-implement-api-auth-to-build-user-authorization-interfaces
https://laravelacademy.org/post/3640.html
参考文章:
https://learnku.com/articles/10885/full-use-of-jwt
https://blog.csdn.net/qq_36514588/article/details/82186617
https://learnku.com/articles/10889/detailed-implementation-of-jwt-extensions