JWT-Json Web Token,一种基于json格式的开放标准,常常被用作替代cookie的认证方式,特别适合前后端分离的WEB应用,以及api接口。今天就讲讲如何在Laravel应用中使用JWT,虽然网上找到的Laravel集成JWT的方法,不过要么就坑点太多,要么就有诸多限制(比如要验证的模型有多个怎么配置)。

实验环境

  • Laravel 5.2+
  • PHP 5.5+
  • tymon/jwt-auth 1.0.0-beta.3 (十分重要)

起步

利用composer安装jwt-atuh

在你的Laravel项目目录里composer.json补充上以下

1
2
3
4
5
{
"required" : {
"tymon/jwt-auth" : "1.0.0-beta.3" # 这个版本很急很关键,我用这个版本才能跑通..Orz
}
}

然后安装一下依赖

1
$ composer update

配置Laravel的服务提供者及其门面

在/config/app.php里找到对应的数组,补充以下东西

1
2
3
4
5
6
7
'providers' => [
Tymon\JWTAuth\Providers\LaravelServiceProvider::class,
]
'aliases' => [
'JWTAuth' => Tymon\JWTAuth\Facades\JWTFactory::class,
]

配置你的用户表(用户模型)

因为Jwt-auth默认用的是Laravel的auth权限管理,Laravel 的权限管理默认用的是项目一开始就有的User模型(/app/User.php),下面姑且先用着Laravel的User模型为例,至于如何自定义用户模型使用其他表,我待会再讲

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
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
// JWT-Auth默认要实现的方法
public function getJWTIdentifier()
{
return $this->getKey();
}
// JWT-Auth默认要实现的方法
public function getJWTCustomClaims()
{
return [];
}
}

可见,配置用户表只需继承Illuminate\Foundation\Auth\User这个类,实现JWTSubject这个接口的两个方法即可。不过这个表必须要有password这个字段,必须有主键, 至于如果你的用户表不用password而是用userPass等等自己自定义的字段名怎么办,这个我待会再讲~


配置Laravel的auth

/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
'defaults' => [
'guard' => 'api', // 此处改成api
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt', // 此处改成jwt
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class, //你实际用到的用户表(用户模型)
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],

生成JWT密钥

1
2
$ php artisan vendor:publish # 将jwt的配置文件自动拉到/config目录
$ php artisan jwt:secret # 自动生成JWT密钥,具体可看.env文件

实际业务逻辑

可以直接在你的控制器里进行调试

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
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Tymon\JWTAuth\Facades\JWTAuth;
class TestController extends Controller
{
/**
* 注册
*/
public function register()
{
User::create([
'name' => 'Dawnki',
'phone' => '188xxxxxx', //此处例子假设以手机为账号
'password' => bcrypt('123456') //加密务必用Laravel中hepler提供的bcrypt
]);
}
/**
* 登录
* @param Request $request
*/
public function login(Request $request)
{
//attempt方法用于验证帐号信息 成功则生成token值
//attempt必须有"password"字段,无论你是自定义密码字段
$token = JWTAuth::attempt(['phone'=>'188xxxxxx','password'=>123456]);
return response(json_encode(['token'=>$token]),200);
}
/**
* 访问内部页面
* 附上Header "Authorization" : "Bearer token值" 注意Bearer与token值间的空格
* @param Request $request
* @return mixed
*/
public function index(Request $request)
{
//return Auth::id(); //直接获取用户id
return Auth::guard('api')->user(); //直接获取用户model
}
}

给index方法(即实际业务里的内部需要登陆后才能访问的接口)编写一个中间件

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
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
/**
* Class UserMiddleware 用户中间件
* @package App\Http\Middleware
*/
class UserMiddleware
{
protected $auth;
function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next, $guard = 'api')
{
if (!($user = Auth::guard($guard)->user())) {
// token值不对 根据实际情况编写返回错误信息,此处返回只作样例
return response('token',403);
}
return $next($request);
}
}

此处只显示中间件的关键方法,至于如何给内部接口(如上例中的index方法)配置中间件自行解决,关键的验证即是 Auth::guard(‘api’)->user();
当前端把Token值放入Header中, 即 “Authorization” : “Bearer token值”(注意Bearer与token值中的空格). Jwt-Auth结合了Laravel的Auth就会通过user()方法自动从Header中取出token值,并且从中解密找出隐含在其中的用户摘要(id).

注意坑点:用户表的加密方式 务必使用bcrypt(即哈希加密),不要使用Crypt门面提供的加密,不要使用Crypt门面提供的加密,不要使用Crypt门面提供的加密!Crypt的加密不适合数据库迁移!

自定义你的用户模型

首先自定义用户模型,不使用自带的User模型

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;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User;
use Tymon\JWTAuth\Contracts\JWTSubject;
class Admin extends User implements JWTSubject
{
protected $table="admin";
protected $primaryKey="admin_id";
protected $fillable=["admin_name","adminPass"];
protected $guarded='';
protected $hidden=["adminPass"];
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
/**
* 重写的方法
*/
public function getAuthPassword()
{
return $this->getAttribute('adminPass'); # 表中密码字段是adminPass
}
}

如上,只需继承User,实现JWTSubject。另外如果你表密码字段不是password,而是其他(如上的adminPass),只需重写getAuthPassword方法(来自于Illuminate\Foundation\Auth\User中用到的trait)即可

然后在/config/auth.php中修改

1
2
3
4
5
6
<?php
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Admin::class, //你需要的类
]

然后你就可以使用自己编写的用户模型,又能搭配上JWT了!

另外此处有一个坑点就是你自定义的密码字段, 在attempt验证账号登陆时是要注意一些细节

1
2
3
4
5
6
7
8
9
10
<?php
/**
* 登录
* @param Request $request
*/
public function login(Request $request)
{
$token = JWTAuth::attempt(['phone'=>'188xxxxxx','password'=>123456]);
return response(json_encode(['token'=>$token]),200);
}

上例,我自定义的密码字段为adminPass,在调用attempt时,不要顺理成章的在验证数组里使用adminPass,依然是使用’password’,这是由于attempt方法内部的限定,在下一篇博客将详细介绍为何要这样~~如果你表里的字段是password的那就没你的事啦,此处只针对自定义的密码字段名


使用两个以上需要权限认证的表

比如一个系统中,有管理员和普通用户,他们分别存在两张不同的表(模型)

只需使用上 Config方法动态修改auth.php的model 即可

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
public function adminLogin(Request $request)
{
Config::set('auth.providers.users.model',\App\Admin::class); #对应管理员表
// Todo your admin login method
}
public function userLogin(Request $request)
{
Config::set('auth.providers.users.model',\App\User::class); #对应用户表
// Todo your user login method
}

总结

刚开始没看Jwt-auth的源码时,配置起来真的很累,用了各种版本都不,最终发现1.0.0-beta.3可以,又根据github里面的issue,找遍了各种自定义方法加上自己琢磨了一下,才能使得根据自己项目的业务需求尽心修改,当中算是了解了jwt-auth是如何跟laravel自带的auth结合在一起的,收获还算可以。

最后附上tymon大大的github,虽然被他的拓展折磨的很惨,不过十分感谢大大的贡献.