laravel5.4 JWT 使用教程
Json web token
什么是JWT
JWT代表Json Web Token.JWT能有效地进行身份验证并连接前后端。
1、JWT定义及其组成
JWT(JSON Web Token)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
载荷(Payload)
我们先将用户认证的操作描述成一个JSON对象。其中添加了一些其他的信息,帮助今后收到这个JWT的服务器理解这个JWT。\
{ "sub": "1", "iss": "http://localhost:8000/auth/login", "iat": 1451888119, "exp": 1454516119, "nbf": 1451888119, "jti": "37c107e4609ddbcc9c096ea5ee76c667" }
这里面的前6个字段都是由JWT的标准所定义的。
- sub: 该JWT所面向的用户
- iss: 该JWT的签发者
- iat(issued at): 在什么时候签发的token
- exp(expires): token什么时候过期
- nbf(not before):token在此时间之前不能被接收处理
- jti:JWT ID为web token提供唯一标识
这些定义都可以在标准中找到。
将上面的JSON对象进行base64编码可以得到下面的字符串:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6Imh0dHA6Ly8xOTIuMTY4LjEuMTUzOjgwMDIvYXBpL2xvZ2luIiwiaWF0IjoxNTA1MjkyMzk5LCJleHAiOjE1MDUyOTU5OTksIm5iZiI6MTUwNTI5MjM5OSwianRpIjoiU3JCOWVISDZTSzBoUVlkcyJ9.8xqHrWMyk9oEMAQ-DLHZDyNlm6N-nJWEjtG7-4aX-F0
这个字符串我们将它称作JWT的Payload(载荷)。
如果你使用Node.js,可以用Node.js的包base64url来得到这个字符串:
var base64url = require('base64url') var header = { "from_user": "B", "target_user": "A" } console.log(base64url(JSON.stringify(header)))
注:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
头部(Header)
JWT还需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象:
{ "typ": "JWT", "alg": "HS256" }
在这里,我们说明了这是一个JWT,并且我们所用的签名算法(后面会提到)是HS256算法。
对它也要进行Base64编码,之后的字符串就成了JWT的Header(头部):
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
签名(签名)
将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWx
ob3N0OjgwMDFcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0NTQ1MTYxMTksIm5iZiI6MTQ1MTg4OD
ExOSwianRpIjoiMzdjMTA3ZTQ2MDlkZGJjYzljMDk2ZWE1ZWU3NmM2NjcifQ
最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret):
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
这样就可以得到我们加密后的内容:
wyoQ95RjAyQ2FF3aj8EvCSaUmeP0KUqcCJDENNfnaT4
这一部分又叫做签名。
最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvbG9jYWx
ob3N0OjgwMDFcL2F1dGhcL2xvZ2luIiwiaWF0IjoxNDUxODg4MTE5LCJleHAiOjE0NTQ1MTYxMTksIm5iZiI6MTQ1MTg4OD
ExOSwianRpIjoiMzdjMTA3ZTQ2MDlkZGJjYzljMDk2ZWE1ZWU3NmM2NjcifQ.wyoQ95RjAyQ2FF3aj8EvCSaUmeP0KUqcCJDENNfnaT4
2、集成JWT到Laravel 5.4
安装
我们使用Composer安装jwt扩展包:
composer require tymon/jwt-auth
配置
安装完成后,需要在 config/app.php添加service provider 和 aliase
:
'providers' => [ .... Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class, ], 'aliases' => [ .... 'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class ],
然后发布相应配置文件,比如令牌到期时间配置等,再config/jwt.php中配置:
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"
最后一步需要生成JWT Key:
php artisan jwt:generate
如果你想要将其添加到.env
文件中,在.env
中创建JWT_SECRET
字段并再次执行生成密钥的命令。
在config/jwt.php中,你可以配置以下选项:
- ttl:token有效期(分钟)
- refresh_ttl:刷新token时间(分钟)
- algo:token签名算法
- user:指向User模型的命名空间路径
- identifier:用于从token的sub中获取用户
- require_claims:必须出现在token的payload中的选项,否则会抛出
TokenInvalidException
异常 - blacklist_enabled:如果该选项被设置为false,那么我们将不能废止token,即使我们刷新了token,前一个token仍然有效
- providers:完成各种任务的具体实现,如果需要的话你可以重写他们
- User —— providers.user:基于sub获取用户的实现
- JWT —— providers.jwt:加密/解密token
- Authentication —— providers.auth:通过证书/ID获取认证用户
- Storage —— providers.storage:存储token直到它们失效
创建API路由
我在创建Api路由的时候会用到一个“cors”中间件,虽然它不是强制性的,但是后面你会发现报类似这样的错:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://xxx.com/api/register. (Reason: CORS header 'Access-Control-Allow-Origin' missing)
大致翻译下,“跨源请求阻塞:同源策略不允许读取http://kylesean.com/api/register远程资源。(原因:CORS 头“Access-Control-Allow-Origin” 没有)。” 这就是跨域请求导致的错误消息,当然你可以自定义Header,Origin, Method来解决跨域问题,不过我这边推荐一个package:barryvdh/laravel-cors,这里安装它:
composer require barryvdh/laravel-cors
安装成功后,创建中间件:
php artisan make:middleware CORS
进入app/Http/Middleware,编辑CORS.php:
namespace App\Http\Middleware; use Closure; class CORS { public function handle($request, Closure $next) { header('Access-Control-Allow-Origin: *'); $headers = [ 'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE', 'Access-Control-Allow-Headers'=> 'Content-Type, X-Auth-Token, Origin' ]; if($request->getMethod() == "OPTIONS") { return Response::make('OK', 200, $headers); } $response = $next($request); foreach($headers as $key => $value) $response->header($key, $value); return $response; } }
在app/Http/Kernel.php注册中间件:
namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { ... ... protected $routeMiddleware = [ ... 'cors' => \App\Http\Middleware\CORS::class, ]; }
有了这个中间件我们就解决了跨域问题。接下来回到路由,app/Http/routes.php:
Route::group(['middleware' => ['api','cors'],'prefix' => 'api'], function () { Route::post('register', 'ApiController@register'); // 注册 Route::post('login', 'ApiController@login'); // 登陆 Route::group(['middleware' => 'jwt.auth'], function () { Route::post('get_user_details', 'APIController@get_user_details'); // 获取用户详情 }); });
上面的jwt-auth中间件现在还是无效的,接着创建这个middleware:
php artisan make:middleware authJWT
同样的我们需要编辑下这个authJWT.php:
namespace App\Http\Middleware; use Closure; use Tymon\JWTAuth\Facades\JWTAuth; use Exception; class authJWT { public function handle($request, Closure $next) { try { // 如果用户登陆后的所有请求没有jwt的token抛出异常 $user = JWTAuth::toUser($request->input('token')); } catch (Exception $e) { if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException){ return response()->json(['error'=>'Token 无效']); }else if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException){ return response()->json(['error'=>'Token 已过期']); }else{ return response()->json(['error'=>'出错了']); } } return $next($request); } }
接着注册该中间件在app/Http/Kernel.php:
namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { ... ... protected $routeMiddleware = [ ... 'jwt.auth' => \App\Http\Middleware\authJWT::class, ]; }
创建控制器管理所有的请求 app/Http/Controllers/ApiController.php:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\User; use Illuminate\Support\Facades\Hash; use Tymon\JWTAuth\Facades\JWTAuth; class ApiController extends Controller { /*注册*/ public function register(Request $request) { $input = $request->all(); $input['password'] = Hash::make($input['password']); User::create($input); return response()->json(['result'=>true]); } /*登陆*/ public function login(Request $request) { $input = $request->all(); if (!$token = JWTAuth::attempt($input)) { return response()->json(['result' => '邮箱或密码错误.']); } return response()->json(['result' => $token]); } /*获取用户信息*/ public function get_user_details(Request $request) { $input = $request->all(); $user = JWTAuth::toUser($input['token']); return response()->json(['result' => $user]); } }
建议:过滤掉路由api/*下的csrf_token,方便测试开发
在App/Http/Middleware/VerifyCsrfToken.php
<?php namespace App\Http\Middleware; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier; class VerifyCsrfToken extends BaseVerifier { /** * The URIs that should be excluded from CSRF verification. * * @var array */ protected $except = [ "api/*" ]; }
最后通过 postman 进行测试:
1、先注册一个账户,POST请求:127.0.0.1/api/regster
这里是我注册的例子,成功返回true:
2、用户登陆:
3、获取用户信息:
https://laravel-china.org/topics/2811/laravel-52-uses-jwt-to-complete-multi-user-authentication