欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

Laravel5.2+Dingo/API+JWTauth 的坑

最近着手做一款应用后端,在否定了 BaaS 后,决定用 Laravel 框架自己做一个 RESTful API。我的环境是 Laravel 5.2 ,另外使用了 Dingo/API 和 JWTAuth。不过在使用的过程中遇到了很多的坑,所以在这里记录一下。

JWTAuth 默认使用 Users 表做为登录认证的表。而我的需求比较奇葩,共有两个不同的表;除此之外,还需要对 JWTAuth 的错误进行自定义。在搜索无果后,只好自己动手实现这两个需求。

首先解决第二个问题,对 JWTAuth 进行错误自定义。这种情况下,我们可以自己去添加一个中间件处理身份认证。

添加中间件处理身份验证

1、添加一个 Middleware

可以使用命令行添加:


1

php artisan make:middleware GetUserFromToken

此命令将会在 app/Http/Middleware 目录内置立一个名称为 GetUserFromToken 的类。

2、在 GetUserFromToken 中编辑代码,这里仿照 JWTAuth 写了 Middleware


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

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
 
class GetUserFromToken
{
public function handle($request, Closure $next)
{
$auth = JWTAuth::parseToken();
if (! $token = $auth->setRequest($request)->getToken()) {
return response()->json([
'code' => '',
'message' => 'token_not_provided',
'data' => '',
]);
}
 
try {
$user = $auth->authenticate($token);
} catch (TokenExpiredException $e) {
return response()->json([
'code' => '',
'message' => 'token_expired',
'data' => '',
]);
} catch (JWTException $e) {
return response()->json([
'code' => '',
'message' => 'token_invalid',
'data' => '',
]);
}
 
if (! $user) {
return response()->json([
'code' => '',
'message' => 'user_not_found',
'data' => '',
]);
}
 
//$this->events->fire('tymon.jwt.valid', $user);
 
return $next($request);
}
}

我将每次错误返回数据替换成自己设置的错误信息。

3、在 /app/Http/Kernel.php 中 $routeMiddleware 新增如下内容:


1
2
3
4

protected $routeMiddleware = [
...
'jwt.api.auth' => \App\Http\Middleware\GetUserFromToken::class, //新增注册的中间件
];

4、在路由中指定使用 jwt.api.auth


1

['middleware' => 'jwt.api.auth']

完成上面的操作,我们新增处理接口身份认证中间件就完成了。

现在需要处理前一个问题。

多表配置

在 JWTAuth 中,可以在配置文件 jwt.php 中设置 User Model namespace,所以可以在 Middleware 中 handle 部分添加如下代码来动态配置 User Model namespace


1

config(['jwt.user' => 'App\Models\User']);

这里,我把 User 表放到了 App\Models\ 中和其他的统一进行管理。不过我在测试中一直出现 App\User 未定义错误。然后就开始了漫长的定位之旅。首先在访问 authenticate 得到


1
2
3
4
5
6
7
8
9
10

public function authenticate($token = false)
{
$id = $this->getPayload($token)->get('sub');
 
if (! $this->auth->byId($id)) {
return false;
}
 
return $this->auth->user();
}

然后,在 Tymon\JWTAuth\Providers\Auth\IlluminateAuthAdapter 中找到 byId 和 user 对应代码如下


1
2
3
4
5
6
7
8
9

public function byId($id)
{
return $this->auth->onceUsingId($id);
}
 
public function user()
{
return $this->auth->user();
}

经过测试发现 auth 实际上是一个 Illuminate\Auth\SessionGuard 实例,然后在其中发现了 onceUsingId 和 user 部分代码


1
2
3
4
5
6
7
8
9
10

public function onceUsingId($id)
{
if (! is_null($user = $this->provider->retrieveById($id))) {
$this->setUser($user);
 
return true;
}
 
return false;
}

在查找 provider 所在位置时定位到文件 Illuminate\Auth\CreatesUserProviders.php 中找到如下代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public function createUserProvider($provider)
{
$config = $this->app['config']['auth.providers.'.$provider];
if (isset($this->customProviderCreators[$config['driver']])) {
return call_user_func(
$this->customProviderCreators[$config['driver']], $this->app, $config
);
}
 
switch ($config['driver']) {
case 'database':
return $this->createDatabaseProvider($config);
case 'eloquent':
return $this->createEloquentProvider($config);
default:
throw new InvalidArgumentException("Authentication user provider [{$config['driver']}] is not defined.");
}
}

这里通过 auth.providers.users 配置设置 $config,而 auth.providers.users 在文件 auth.php 中默认配置如下


1
2
3
4
5
6

'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
]

所以程序走到了 return $this->createEloquentProvider($config); 这一步,继续跟踪得到:


1
2
3
4

protected function createEloquentProvider($config)
{
return new EloquentUserProvider($this->app['hash'], $config['model']);
}

其中 $config['model'] 则就是原型:


1
2
3
4
5

public function __construct(HasherContract $hasher, $model)
{
$this->model = $model;
$this->hasher = $hasher;
}

到此,确定了 model 所在位置,只需要在 Middleware 中添加如下配置


1

config(['auth.providers.users.model' => \App\Models\User::class]);

最终代码如下


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

config(['jwt.user' => '\App\Models\User']);
config(['auth.providers.users.model' => \App\Models\User::class]);
$auth = JWTAuth::parseToken();
if (! $token = $auth->setRequest($request)->getToken()) {
return response()->json([
'code' => '',
'message' => 'token_not_provided',
'data' => '',
]);
}
 
try {
$user = $auth->authenticate($token);
} catch (TokenExpiredException $e) {
return response()->json([
'code' => '',
'message' => 'token_expired',
'data' => '',
]);
} catch (JWTException $e) {
return response()->json([
'code' => '',
'message' => 'token_invalid',
'data' => '',
]);
}
 
if (! $user) {
return response()->json([
'code' => '',
'message' => 'user_not_found',
'data' => '',
]);
}
 
//$this->events->fire('tymon.jwt.valid', $user);
 
return $next($request);

到这里为止,实现了自定义表名功能,在结合自定义 Middleware 部分,就可以实现多表认证。只需要对每一种认证都实现对应的 Middleware ,在接口处分别对不同接口使用不同的 Middleware 进行验证就好。

当然,这样的实现肯定不完美,因为所有的事件部分代码全部删除了。这部分还没有想到什么好的解决办法,自己实现 event 应该是可行的,这里就么有尝试。

来自 http://www.hashcoding.net/2016/04/28/Laravel5-2-Dingo-API-JWTauth-%E7%9A%84%E5%9D%91/

普通分类: