Json Web Token
JWT在Laravel5.2中的应用
JWT代表Json Web Token.JWT能有效地进行身份验证并连接前后端。
降地耦合性,取代session,进一步实现前后端分离
减少服务器的压力
可以很简单的实现单点登录
实现API 安装扩展 1 composer require tymon/jwt-auth
之后打开config/app.php文件添加service provider 和 aliase
config/app.php 1 2 3 4 5 6 7 8 'providers' => [ .... Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class , // 注意这里的名字,下文会提到 ], 'aliases' => [ .... 'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class ],
OK,现在来发布JWT的配置文件,比如令牌到期时间配置等
1 php artisan vendor:publish --provider= "Tymon\JWTAuth\Providers\JWTAuthServiceProvider"
最后一步需要生成JWT Key
1 php artisan jwt:generate
创建API路由 我在创建Api路由的时候会用到一个“cors”
中间件,虽然它不是强制性的,但是后面你会发现报类似这样的错
1 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(最新稳定版是0.8.2),这里安装过程省略。
创建中间件 1 php artisan make :middleware CORS
进入app/Http/Middleware,编辑CORS.php
app/Http/Middleware/CORS.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 ; } }
Ok,在app/Http/Kernel.php注册中间件
app/Http/Kernel.php 1 2 3 4 5 6 7 8 9 10 11 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 1 2 3 4 5 6 7 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' ); }); });
建议:过滤掉路由api/*下的csrf_token
,方便测试开发
上面的jwt-auth中间件现在还是无效的,接着创建这个middleware
1 php artisan make :middleware authJWT
同样的我们需要编辑下这个authJWT.php
app/Http/Middleware/authJWT.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 namespace App \Http \Middleware ;use Closure ;use Tymon \JWTAuth \Facades \JWTAuth ;use Exception ;class authJWT { public function handle ($request , Closure $next ) { try { $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 ); } }
OK,接着注册该中间件
app/Http/Kernel.php 1 2 3 4 5 6 7 8 9 10 11 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?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,为了模拟本地跨域请求,我们简单的新建一个静态页面test.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > Document</title > <script src ="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js" > </script > </head > <body > </body > <script > $.ajax ( { url: "http://localhost/api/login", dataType: "json", type: "POST", data: {"email":kylesean@qq.com","password":"123456"} , success : function (data ) { alert(data.result) } }); </script > </html >
这里我们要注意一下,以上测试我们仍是基于User table的,我们来模拟一下login过程,如果账号密码匹配成功,不出意外将会出现类似:
1 2 3 { "result" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImlzcyI6Imh0dHA6XC9cL2xvY2FsaG9zdFwvYXBpXC9sb2dpbiIsImlhdCI6MTQ3MzQ1MjUyNSwiZXhwIjoxNDczNDU2MTI1LCJuYmYiOjE0NzM0NTI1MjUsImp0aSI6IjA1M2IzNjliYzYyZjJiZjJmMGMxNjFiNzIxNzY4Y2MzIn0.4WeezpSgEKjNmDFxv1nMU9HxqJgBE7bPyaJDRK4iLeA" }
至此,我们已经实现了jwt的认证功能,那么我们接着完成下一半工作,实现jwt的多用户认证,即Jwt for Multi Auth.
如果你的业务场景是的确需要多用户认证,比如为管理员admin单独生成一张表,恰好字段也是laravel auth user里面默认的name email password remember_token等,那么实现起来就方便的多,官方文档和网上的demo示例已经很多了,但是若结合这个laravel/jwt-auth扩展进行多用户认证,其实坑还是蛮多的,由于该扩展0.5.9似乎不支持多用户认证(反正不会帮我们自定义好guard,当然我们可以自己在AuthServiceProvider里用boot方法实现) 我在github issue
里面看到好多人踩过此坑,结合我遇到的
总结一下,里面一个哥们说,得用^0.1@dev版本(什么鬼,what’s the fuck!),so 继续撸之:
实现JWT的多用户认证 composer.json里修改为 1 2 3 4 5 "require" : { ... "tymon/jwt-auth" : "^1.0@dev" , ... }
同样app.php里进行配置 1 2 3 4 5 6 7 8 'providers' => [ .... Tymon\JWTAuth\Providers\LaravelServiceProvider::class , // 上文已经提到过,这里的provider 已经不是JWTauthServiceProvider ], 'aliases' => [ .... 'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class ],
发布配置文件 1 php artisan vendor:publish --provider= "Tymon\JWTAuth\Providers\LaravelServiceProvider"
生成密钥 1 php artisan jwt:secret // 发现没 生成key的方法也变了,不是 php artisan jwt:generate了
接下来就是重点了,要设置好config/auth.php里面的配置项了,这里不能乱设置:
config/auth.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 'defaults' => [ 'guard' => 'web' , 'passwords' => 'users' , ], 'guards' => [ 'web' => [ 'driver' => 'session' , 'provider' => 'users' , ], 'api' => [ 'driver' => 'token' , 'provider' => 'users' , ], 'staffs' => [ 'driver' => 'jwt' , 'provider' => 'staffs' ] ], 'providers' => [ 'users' => [ 'driver' => 'eloquent' , 'model' => App\User::class, ], 'staffs' => [ 'driver' => 'eloquent' , 'model' => App\Models\Staff::class, ] ], 'passwords' => [ 'users' => [ 'provider' => 'users' , 'email' => 'auth.emails.password' , 'table' => 'password_resets' , 'expire' => 60 , ], <!-- 'staffs' => [ 'provider' => 'staffs' , 'email' => 'auth.emails.password' , 'table' => 'password_resets' , 'expire' => 60 , ],--> ]
下一步,创建我们的staff model
Models\staff.php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?php namespace App \Models ;use Illuminate \Auth \Authenticatable ;use Illuminate \Database \Eloquent \Model ;use Illuminate \Auth \Passwords \CanResetPassword ;use Illuminate \Foundation \Auth \Access \Authorizable ;use Illuminate \Contracts \Auth \Authenticatable as AuthenticatableContract ;use Illuminate \Contracts \Auth \Access \Authorizable as AuthorizableContract ;use Tymon \JWTAuth \Contracts \JWTSubject as AuthenticatableUserContract ; class Staff extends Model implements AuthenticatableContract , AuthorizableContract , AuthenticatableUserContract { use Authenticatable , Authorizable , CanResetPassword ; protected $table = 'staffs' ; protected $fillable = ['name' , 'phone' , 'password' ]; protected $hidden = ['password' , 'remember_token' ]; public function getJWTIdentifier ( ) { return $this ->getKey (); } public function getJWTCustomClaims ( ) { return []; } }
好吧,接下来我们又要添加相关路由了
1 2 Route::post ('/api/login' , 'StaffAuthController@login' ); Route::post ('/api/register' , 'StaffAuthController@register' );
控制器书写我们的业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 namespace App \Http \Controllers \Staff ;use App \Models \Staff ;use Illuminate \Http \Request ;use App \Http \Controllers \Controller ;use App \Http \Requests ;use Illuminate \Foundation \Auth \ThrottlesLogins ;use Illuminate \Foundation \Auth \AuthenticatesAndRegistersUsers ;use Illuminate \Support \Facades \Validator ;use Tymon \JWTAuth \Facades \JWTAuth ;use Illuminate \Support \Facades \Auth ;class StaffAuthController extends Controller { use AuthenticatesAndRegistersUsers , ThrottlesLogins ; protected $guard = 'staffs' ; public function register (Request $request ) { $this ->validate ($request , [ 'phone' => 'required|max:16' , 'password' => 'required|min:6' , ]); $credentials = [ 'phone' => $request ->input ('phone' ), 'password' => bcrypt ($request ->input ('password' )), ]; $id = Staff ::create ($credentials ); if ($id ) { $token = Auth ::guard ($this ->getGuard ())->attempt ($credentials ); return response ()->json (['result' => $token ]); } } public function login (Request $request ) { $credentials = $request ->only ('phone' ,'password' ); if ( $token = Auth ::guard ($this ->getGuard ())->attempt ($credentials ) ) { return response ()->json (['result' => $token ]); } else { return response ()->json (['result' =>false ]); } } {
到现在,一个基于JWT的多用于认证系统雏形就建立就来了,这里面需要完善的东西很多,比如刷新token,退出登录,增加额外的中间件等,
参考